init projectmycelium
This commit is contained in:
257
tests/tests_archive/README.md
Normal file
257
tests/tests_archive/README.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# Project Mycelium Testing Guide
|
||||
|
||||
This directory contains tests for the Project Mycelium application. This guide explains how to write and run tests for various marketplace components.
|
||||
|
||||
## Test Structure
|
||||
|
||||
### Directory Layout
|
||||
```
|
||||
tests/
|
||||
├── README.md # This guide
|
||||
├── test_tfp_fix.rs # TFP purchase data preservation test
|
||||
└── [future test files] # Additional tests as needed
|
||||
```
|
||||
|
||||
## Types of Tests
|
||||
|
||||
### 1. Unit Tests
|
||||
Test individual functions and components in isolation.
|
||||
|
||||
**Example: Testing Currency Conversion**
|
||||
```rust
|
||||
// tests/test_currency.rs
|
||||
fn main() {
|
||||
println!("=== Currency Conversion Test ===");
|
||||
|
||||
let base_amount = 100.0;
|
||||
let conversion_rate = 0.1; // 1 TFP = 0.1 USD
|
||||
let expected_usd = base_amount * conversion_rate;
|
||||
|
||||
println!("Converting {} TFP to USD", base_amount);
|
||||
println!("Expected: ${}", expected_usd);
|
||||
|
||||
// Test logic here
|
||||
assert_eq!(expected_usd, 10.0);
|
||||
println!("✅ Currency conversion test passed!");
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Integration Tests
|
||||
Test how different components work together.
|
||||
|
||||
**Example: Testing User Data Persistence**
|
||||
```rust
|
||||
// tests/test_user_persistence.rs
|
||||
fn main() {
|
||||
println!("=== User Data Persistence Test ===");
|
||||
|
||||
// Simulate user data operations
|
||||
let user_email = "test@example.com";
|
||||
let initial_balance = 1000.0;
|
||||
let services_count = 3;
|
||||
|
||||
// Test data preservation during operations
|
||||
println!("Testing data persistence for user: {}", user_email);
|
||||
println!("Initial balance: {}", initial_balance);
|
||||
println!("Services count: {}", services_count);
|
||||
|
||||
// Verify data integrity
|
||||
println!("✅ User data persistence test passed!");
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Bug Fix Verification Tests
|
||||
Test specific bug fixes to ensure they work correctly.
|
||||
|
||||
**Example: TFP Purchase Fix Test** (see [`test_tfp_fix.rs`](test_tfp_fix.rs))
|
||||
- Tests that TFP purchases preserve existing user data
|
||||
- Verifies wallet balance updates correctly
|
||||
- Ensures services, apps, and profile data are not lost
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Simple Rust Tests
|
||||
For standalone test files without external dependencies:
|
||||
|
||||
```bash
|
||||
# Compile and run a specific test
|
||||
rustc tests/test_name.rs && ./test_name
|
||||
|
||||
# Example: Run TFP fix test
|
||||
rustc tests/test_tfp_fix.rs && ./test_tfp_fix
|
||||
```
|
||||
|
||||
### Cargo Tests
|
||||
For tests that need access to the main crate:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
cargo test
|
||||
|
||||
# Run specific test
|
||||
cargo test test_name
|
||||
|
||||
# Run tests with output
|
||||
cargo test -- --nocapture
|
||||
```
|
||||
|
||||
## Writing Effective Tests
|
||||
|
||||
### 1. Test Structure
|
||||
Follow the **Arrange-Act-Assert** pattern:
|
||||
|
||||
```rust
|
||||
fn test_example() {
|
||||
// ARRANGE: Set up test data
|
||||
let initial_data = setup_test_data();
|
||||
|
||||
// ACT: Perform the operation being tested
|
||||
let result = perform_operation(initial_data);
|
||||
|
||||
// ASSERT: Verify the results
|
||||
assert_eq!(result.status, "success");
|
||||
println!("✅ Test passed!");
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Test Categories
|
||||
|
||||
#### Data Integrity Tests
|
||||
- Verify data is preserved during operations
|
||||
- Test backup and recovery mechanisms
|
||||
- Ensure no data loss during updates
|
||||
|
||||
#### Business Logic Tests
|
||||
- Test marketplace transactions
|
||||
- Verify pricing calculations
|
||||
- Test user role permissions
|
||||
|
||||
#### API Endpoint Tests
|
||||
- Test HTTP request/response handling
|
||||
- Verify authentication and authorization
|
||||
- Test error handling and edge cases
|
||||
|
||||
#### Performance Tests
|
||||
- Test response times under load
|
||||
- Verify memory usage patterns
|
||||
- Test concurrent user scenarios
|
||||
|
||||
### 3. Test Data Management
|
||||
|
||||
#### Mock Data
|
||||
Use consistent test data across tests:
|
||||
|
||||
```rust
|
||||
fn create_test_user() -> TestUser {
|
||||
TestUser {
|
||||
email: "test@example.com".to_string(),
|
||||
balance: 1000.0,
|
||||
services: vec!["Service 1".to_string(), "Service 2".to_string()],
|
||||
apps: vec!["App 1".to_string()],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Test Isolation
|
||||
Ensure tests don't interfere with each other:
|
||||
- Use unique test data for each test
|
||||
- Clean up after tests when necessary
|
||||
- Avoid shared mutable state
|
||||
|
||||
## Marketplace-Specific Testing
|
||||
|
||||
### 1. User Management Tests
|
||||
- User registration and authentication
|
||||
- Profile updates and data persistence
|
||||
- Account deletion and recovery
|
||||
|
||||
### 2. Wallet and Transaction Tests
|
||||
- TFP purchases and sales
|
||||
- Balance updates and transaction history
|
||||
- Currency conversion accuracy
|
||||
|
||||
### 3. Service Provider Tests
|
||||
- Service creation and management
|
||||
- SLA compliance and monitoring
|
||||
- Revenue tracking and reporting
|
||||
|
||||
### 4. App Provider Tests
|
||||
- App deployment and management
|
||||
- Health monitoring and auto-healing
|
||||
- Resource utilization tracking
|
||||
|
||||
### 5. Marketplace Operations Tests
|
||||
- Product search and filtering
|
||||
- Shopping cart functionality
|
||||
- Order processing and fulfillment
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Test Naming
|
||||
Use descriptive names that explain what is being tested:
|
||||
- `test_tfp_purchase_preserves_user_data`
|
||||
- `test_currency_conversion_accuracy`
|
||||
- `test_service_creation_validation`
|
||||
|
||||
### 2. Error Testing
|
||||
Always test error conditions:
|
||||
- Invalid input data
|
||||
- Network failures
|
||||
- Database connection issues
|
||||
- Authentication failures
|
||||
|
||||
### 3. Edge Cases
|
||||
Test boundary conditions:
|
||||
- Zero amounts and empty data
|
||||
- Maximum limits and constraints
|
||||
- Concurrent operations
|
||||
- System resource limits
|
||||
|
||||
### 4. Documentation
|
||||
Document complex test scenarios:
|
||||
- Explain the business logic being tested
|
||||
- Describe the expected behavior
|
||||
- Note any special setup requirements
|
||||
|
||||
## Example Test Scenarios
|
||||
|
||||
### Scenario 1: TFP Purchase Data Preservation
|
||||
**Problem**: TFP purchases were removing all other user data
|
||||
**Test**: Verify that after a TFP purchase, all existing user data (services, apps, profile) is preserved
|
||||
**File**: [`test_tfp_fix.rs`](test_tfp_fix.rs)
|
||||
|
||||
### Scenario 2: Service Provider Revenue Tracking
|
||||
**Test**: Verify that service provider revenue is calculated correctly across multiple clients
|
||||
**Expected**: Revenue should accumulate properly without data loss
|
||||
|
||||
### Scenario 3: App Auto-Healing Functionality
|
||||
**Test**: Verify that apps with auto-healing enabled recover from simulated failures
|
||||
**Expected**: Health scores should improve after recovery
|
||||
|
||||
## Contributing Tests
|
||||
|
||||
When adding new tests:
|
||||
|
||||
1. **Follow naming conventions**: `test_[component]_[functionality].rs`
|
||||
2. **Add documentation**: Explain what the test does and why
|
||||
3. **Include in this README**: Update this guide with new test categories
|
||||
4. **Test the test**: Ensure your test can detect both success and failure conditions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Compilation Errors**: Ensure all required dependencies are available
|
||||
2. **Test Failures**: Check that test data matches expected formats
|
||||
3. **Permission Issues**: Verify file system permissions for data files
|
||||
4. **Concurrency Issues**: Ensure tests don't interfere with each other
|
||||
|
||||
### Getting Help
|
||||
|
||||
- Check the main [`MARKETPLACE_ARCHITECTURE.md`](../MARKETPLACE_ARCHITECTURE.md) for system overview
|
||||
- Review existing tests for patterns and examples
|
||||
- Consult the Rust testing documentation for advanced features
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Good tests are an investment in code quality and system reliability. They help catch bugs early and provide confidence when making changes to the marketplace codebase.
|
128
tests/tests_archive/instant_purchase_persistence_test.rs
Normal file
128
tests/tests_archive/instant_purchase_persistence_test.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use actix_web::{test, App, http::StatusCode, web};
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
use tera::Tera;
|
||||
|
||||
use threefold_marketplace::routes::configure_routes;
|
||||
use threefold_marketplace::services::user_persistence::UserPersistence;
|
||||
use threefold_marketplace::utils;
|
||||
use threefold_marketplace::models::user::Service;
|
||||
|
||||
fn sanitize(email: &str) -> String {
|
||||
email.replace("@", "_at_").replace(".", "_")
|
||||
}
|
||||
|
||||
/// Helper to read a Set-Cookie header by name
|
||||
fn extract_cookie_value(resp: &actix_web::dev::ServiceResponse, name: &str) -> Option<String> {
|
||||
for header in resp.headers().get_all(actix_web::http::header::SET_COOKIE) {
|
||||
if let Ok(s) = header.to_str() {
|
||||
if s.starts_with(&format!("{}=", name)) {
|
||||
// Take only the name=value part
|
||||
if let Some((prefix, _rest)) = s.split_once(';') {
|
||||
return Some(prefix.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_instant_purchase_persists_service_booking() {
|
||||
// Ensure clean user_data directory entries for test users
|
||||
let _ = std::fs::create_dir_all("user_data");
|
||||
let customer_email = "instant_customer@example.com";
|
||||
let provider_email = "provider_instant@example.com";
|
||||
let product_id = "svc-test-123";
|
||||
|
||||
let customer_path = format!("user_data/{}.json", sanitize(customer_email));
|
||||
let provider_path = format!("user_data/{}.json", sanitize(provider_email));
|
||||
let _ = std::fs::remove_file(&customer_path);
|
||||
let _ = std::fs::remove_file(&provider_path);
|
||||
|
||||
// Initialize Tera templates similar to main.rs
|
||||
let mut tera = Tera::new("src/views/**/*.html").expect("failed to load templates");
|
||||
utils::register_tera_functions(&mut tera);
|
||||
|
||||
// Start test app with configured routes (includes session middleware)
|
||||
let app = test::init_service(
|
||||
App::new()
|
||||
.app_data(web::Data::new(tera))
|
||||
.configure(configure_routes)
|
||||
).await;
|
||||
|
||||
// 1) Register a new customer to obtain a valid session cookie
|
||||
let form = [
|
||||
("name", "Instant Customer"),
|
||||
("email", customer_email),
|
||||
("password", "secret123"),
|
||||
("password_confirmation", "secret123"),
|
||||
];
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/register")
|
||||
.set_form(&form)
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_redirection());
|
||||
|
||||
// Extract session cookie set by session middleware
|
||||
let session_cookie = extract_cookie_value(&resp, "threefold_marketplace_session")
|
||||
.expect("session cookie should be set after registration");
|
||||
|
||||
// 2) Seed provider user_data with a service matching product_id
|
||||
let mut provider_data = UserPersistence::create_default_user_data(provider_email);
|
||||
provider_data.services.push(Service {
|
||||
id: product_id.to_string(),
|
||||
name: "Instant Test Service".to_string(),
|
||||
category: "service".to_string(),
|
||||
description: "test service".to_string(),
|
||||
price_per_hour_usd: 100,
|
||||
status: "active".to_string(),
|
||||
clients: 0,
|
||||
rating: 0.0,
|
||||
total_hours: 0,
|
||||
});
|
||||
UserPersistence::save_user_data(&provider_data).expect("save provider data");
|
||||
|
||||
// 3) Increase customer's wallet balance so purchase can succeed
|
||||
let mut customer_data = UserPersistence::create_default_user_data(customer_email);
|
||||
customer_data.name = Some("Instant Customer".to_string());
|
||||
customer_data.wallet_balance_usd = Decimal::from(500);
|
||||
UserPersistence::save_user_data(&customer_data).expect("save customer data");
|
||||
|
||||
// 4) Perform instant purchase
|
||||
let body = json!({
|
||||
"product_id": product_id,
|
||||
"product_name": "Instant Test Service",
|
||||
"product_category": "service",
|
||||
"quantity": 1u32,
|
||||
"unit_price_usd": Decimal::from(100),
|
||||
"provider_id": provider_email,
|
||||
"provider_name": "Instant Provider",
|
||||
"specifications": json!({"note":"test"}),
|
||||
});
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/api/wallet/instant-purchase")
|
||||
.insert_header((actix_web::http::header::CONTENT_TYPE, "application/json"))
|
||||
.insert_header((actix_web::http::header::COOKIE, session_cookie.clone()))
|
||||
.set_payload(body.to_string())
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let resp_body: serde_json::Value = test::read_body_json(resp).await;
|
||||
assert!(resp_body.get("success").and_then(|v| v.as_bool()).unwrap_or(false));
|
||||
|
||||
// 5) Verify that a booking got persisted to the customer's file
|
||||
let persisted = UserPersistence::load_user_data(customer_email).expect("customer data should exist");
|
||||
assert!(persisted.service_bookings.len() >= 1, "expected at least one service booking to be persisted");
|
||||
assert!(persisted.service_bookings.iter().any(|b| b.service_name == "Instant Test Service"));
|
||||
|
||||
// Also verify provider got a service request
|
||||
let provider_persisted = UserPersistence::load_user_data(provider_email).expect("provider data should exist");
|
||||
assert!(provider_persisted.service_requests.len() >= 1, "expected at least one service request for provider");
|
||||
}
|
57
tests/tests_archive/mock_gating.rs
Normal file
57
tests/tests_archive/mock_gating.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use actix_web::{test, App, http::StatusCode, web};
|
||||
use tera::Tera;
|
||||
use threefold_marketplace::config::builder::{init_app_config, ConfigurationBuilder};
|
||||
use threefold_marketplace::routes::configure_routes;
|
||||
use threefold_marketplace::utils;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_rent_product_returns_404_when_mocks_disabled() {
|
||||
// Initialize global config with mocks disabled before app starts
|
||||
let mut cfg = ConfigurationBuilder::testing().build();
|
||||
cfg.enable_mock_data = false;
|
||||
// It's okay if already set in other tests; if set, this will Err and we skip
|
||||
let _ = init_app_config(cfg);
|
||||
|
||||
// Initialize Tera templates and register custom functions (mirror main.rs)
|
||||
let mut tera = Tera::new("src/views/**/*.html").expect("failed to load templates");
|
||||
utils::register_tera_functions(&mut tera);
|
||||
|
||||
let app = test::init_service(
|
||||
App::new()
|
||||
.app_data(web::Data::new(tera))
|
||||
.configure(configure_routes)
|
||||
).await;
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/api/products/test-product/rent")
|
||||
.set_json(&serde_json::json!({ "duration": "monthly" }))
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_marketplace_dashboard_loads_with_mocks_disabled() {
|
||||
// Initialize global config with mocks disabled before app starts
|
||||
let mut cfg = ConfigurationBuilder::testing().build();
|
||||
cfg.enable_mock_data = false;
|
||||
let _ = init_app_config(cfg);
|
||||
|
||||
// Initialize Tera templates and register custom functions (mirror main.rs)
|
||||
let mut tera = Tera::new("src/views/**/*.html").expect("failed to load templates");
|
||||
utils::register_tera_functions(&mut tera);
|
||||
|
||||
let app = test::init_service(
|
||||
App::new()
|
||||
.app_data(web::Data::new(tera))
|
||||
.configure(configure_routes)
|
||||
).await;
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.uri("/marketplace")
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
197
tests/tests_archive/service_booking_integration_test.rs
Normal file
197
tests/tests_archive/service_booking_integration_test.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! Integration test for the complete service booking workflow
|
||||
//! Tests the end-to-end flow: Service Creation → Marketplace → Purchase → Customer Tracking
|
||||
|
||||
use threefold_marketplace::models::user::{User, MockUserData, ServiceBooking, CustomerServiceData, SpendingRecord};
|
||||
use threefold_marketplace::models::builders::{ServiceBookingBuilder, CustomerServiceDataBuilder, SpendingRecordBuilder};
|
||||
use threefold_marketplace::services::order::OrderService;
|
||||
use threefold_marketplace::services::user_persistence::UserPersistence;
|
||||
use chrono::Utc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(test)]
|
||||
mod service_booking_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_service_booking_data_structures() {
|
||||
// Test ServiceBooking creation with builder pattern
|
||||
let booking = ServiceBookingBuilder::new()
|
||||
.id("test_booking_001")
|
||||
.service_id("service_001")
|
||||
.service_name("Cloud Migration Consulting")
|
||||
.provider_email("provider@example.com")
|
||||
.customer_email("customer@example.com")
|
||||
.budget(1000)
|
||||
.estimated_hours(20)
|
||||
.status("In Progress")
|
||||
.requested_date("2025-06-20")
|
||||
.priority("High")
|
||||
.description(Some("Test migration project".to_string()))
|
||||
.booking_date("2025-06-15")
|
||||
.build()
|
||||
.expect("Should create service booking");
|
||||
|
||||
assert_eq!(booking.id, "test_booking_001");
|
||||
assert_eq!(booking.service_name, "Cloud Migration Consulting");
|
||||
assert_eq!(booking.budget, 1000);
|
||||
assert_eq!(booking.status, "In Progress");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_customer_service_data_creation() {
|
||||
// Create test service bookings
|
||||
let booking1 = ServiceBookingBuilder::new()
|
||||
.id("booking_001")
|
||||
.service_id("service_001")
|
||||
.service_name("Cloud Migration")
|
||||
.provider_email("provider1@example.com")
|
||||
.customer_email("customer@example.com")
|
||||
.budget(800)
|
||||
.estimated_hours(16)
|
||||
.status("In Progress")
|
||||
.requested_date("2025-06-20")
|
||||
.priority("High")
|
||||
.booking_date("2025-06-15")
|
||||
.build()
|
||||
.expect("Should create booking 1");
|
||||
|
||||
let booking2 = ServiceBookingBuilder::new()
|
||||
.id("booking_002")
|
||||
.service_id("service_002")
|
||||
.service_name("DevOps Consultation")
|
||||
.provider_email("provider2@example.com")
|
||||
.customer_email("customer@example.com")
|
||||
.budget(600)
|
||||
.estimated_hours(12)
|
||||
.status("Pending")
|
||||
.requested_date("2025-06-25")
|
||||
.priority("Medium")
|
||||
.booking_date("2025-06-18")
|
||||
.build()
|
||||
.expect("Should create booking 2");
|
||||
|
||||
// Create spending history
|
||||
let spending1 = SpendingRecordBuilder::new()
|
||||
.date("2025-06-15")
|
||||
.amount(800)
|
||||
.service_name("Cloud Migration")
|
||||
.provider_name("Provider One")
|
||||
.build()
|
||||
.expect("Should create spending record 1");
|
||||
|
||||
let spending2 = SpendingRecordBuilder::new()
|
||||
.date("2025-05-20")
|
||||
.amount(400)
|
||||
.service_name("Security Audit")
|
||||
.provider_name("Security Expert")
|
||||
.build()
|
||||
.expect("Should create spending record 2");
|
||||
|
||||
// Create customer service data
|
||||
let customer_data = CustomerServiceDataBuilder::new()
|
||||
.active_bookings(2)
|
||||
.completed_bookings(3)
|
||||
.total_spent(2000)
|
||||
.monthly_spending(400)
|
||||
.average_rating_given(4.5)
|
||||
.service_bookings(vec![booking1, booking2])
|
||||
.favorite_providers(vec!["provider1@example.com".to_string(), "provider2@example.com".to_string()])
|
||||
.spending_history(vec![spending1, spending2])
|
||||
.build()
|
||||
.expect("Should create customer service data");
|
||||
|
||||
assert_eq!(customer_data.active_bookings, 2);
|
||||
assert_eq!(customer_data.completed_bookings, 3);
|
||||
assert_eq!(customer_data.total_spent, 2000);
|
||||
assert_eq!(customer_data.service_bookings.len(), 2);
|
||||
assert_eq!(customer_data.favorite_providers.len(), 2);
|
||||
assert_eq!(customer_data.spending_history.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mock_user_data_with_customer_service_data() {
|
||||
// Test that user1 has customer service data
|
||||
let user1_data = MockUserData::user1();
|
||||
|
||||
assert!(user1_data.customer_service_data.is_some());
|
||||
|
||||
let customer_data = user1_data.customer_service_data.unwrap();
|
||||
assert_eq!(customer_data.active_bookings, 2);
|
||||
assert_eq!(customer_data.completed_bookings, 5);
|
||||
assert!(customer_data.total_spent > 0);
|
||||
assert!(!customer_data.service_bookings.is_empty());
|
||||
assert!(!customer_data.favorite_providers.is_empty());
|
||||
assert!(!customer_data.spending_history.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_customer_users_have_no_service_data() {
|
||||
// Test that users who aren't customers have None for customer_service_data
|
||||
let user2_data = MockUserData::user2(); // App provider
|
||||
let user3_data = MockUserData::user3(); // Power user
|
||||
let user4_data = MockUserData::user4(); // Regular user
|
||||
let user5_data = MockUserData::user5(); // Service provider
|
||||
let new_user_data = MockUserData::new_user();
|
||||
|
||||
assert!(user2_data.customer_service_data.is_none());
|
||||
assert!(user3_data.customer_service_data.is_none());
|
||||
assert!(user4_data.customer_service_data.is_none());
|
||||
assert!(user5_data.customer_service_data.is_none());
|
||||
assert!(new_user_data.customer_service_data.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service_booking_workflow_integration() {
|
||||
// This test simulates the complete workflow:
|
||||
// 1. Customer (user1) has service bookings
|
||||
// 2. Service provider (user5) has service requests
|
||||
// 3. The booking IDs should match between customer and provider
|
||||
|
||||
let customer_data = MockUserData::user1();
|
||||
let provider_data = MockUserData::user5();
|
||||
|
||||
// Verify customer has service bookings
|
||||
assert!(customer_data.customer_service_data.is_some());
|
||||
let customer_service_data = customer_data.customer_service_data.unwrap();
|
||||
assert!(!customer_service_data.service_bookings.is_empty());
|
||||
|
||||
// Verify provider has service provider data
|
||||
assert!(provider_data.service_provider_data.is_some());
|
||||
let provider_service_data = provider_data.service_provider_data.unwrap();
|
||||
|
||||
// In a real implementation, we would verify that booking IDs match
|
||||
// between customer bookings and provider requests
|
||||
println!("✅ Customer has {} active bookings", customer_service_data.active_bookings);
|
||||
println!("✅ Provider has {} active services", provider_service_data.active_services);
|
||||
|
||||
// Verify the data structure integrity
|
||||
for booking in &customer_service_data.service_bookings {
|
||||
assert!(!booking.id.is_empty());
|
||||
assert!(!booking.service_name.is_empty());
|
||||
assert!(!booking.provider_email.is_empty());
|
||||
assert!(!booking.customer_email.is_empty());
|
||||
assert!(booking.budget > 0);
|
||||
assert!(booking.estimated_hours > 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spending_calculation() {
|
||||
let user1_data = MockUserData::user1();
|
||||
let customer_data = user1_data.customer_service_data.unwrap();
|
||||
|
||||
// Verify spending calculations make sense
|
||||
assert!(customer_data.total_spent >= customer_data.monthly_spending);
|
||||
assert!(customer_data.average_rating_given >= 0.0 && customer_data.average_rating_given <= 5.0);
|
||||
|
||||
// Verify spending history totals
|
||||
let history_total: i32 = customer_data.spending_history.iter()
|
||||
.map(|record| record.amount)
|
||||
.sum();
|
||||
|
||||
// The history might not include all spending, but should be reasonable
|
||||
assert!(history_total > 0);
|
||||
println!("✅ Total spending from history: {} TFP", history_total);
|
||||
println!("✅ Total customer spending: {} TFP", customer_data.total_spent);
|
||||
}
|
||||
}
|
98
tests/tests_archive/ssh_key_complete_fix_test.rs
Normal file
98
tests/tests_archive/ssh_key_complete_fix_test.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::fs;
|
||||
use threefold_marketplace::services::ssh_key_service::SSHKeyService;
|
||||
|
||||
/// Final comprehensive test to verify SSH key button fixes are working
|
||||
#[tokio::test]
|
||||
async fn test_ssh_key_complete_fix_verification() {
|
||||
println!("🔧 Testing SSH Key Complete Fix Verification");
|
||||
|
||||
// Test user
|
||||
let test_user_email = "fix_verification@example.com";
|
||||
let test_ssh_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQ4Iz1Pj5PjRrxeL5LfFnGe3w9vNNjc+FW7gX6H5sAB fix_test@example.com";
|
||||
|
||||
// Clean up any existing test data
|
||||
let user_data_path = format!("user_data/{}.json", test_user_email.replace("@", "_at_").replace(".", "_"));
|
||||
let _ = fs::remove_file(&user_data_path);
|
||||
|
||||
println!("🔧 Step 1: Verify SSH Key Service Operational");
|
||||
let ssh_service = SSHKeyService::builder().build().expect("Failed to build SSH key service");
|
||||
println!("✅ SSH Key Service: Operational");
|
||||
|
||||
println!("🔧 Step 2: Create Test SSH Key");
|
||||
let created_key = ssh_service.add_ssh_key(
|
||||
test_user_email,
|
||||
"Complete Fix Test Key",
|
||||
test_ssh_key,
|
||||
false
|
||||
).expect("Failed to add SSH key");
|
||||
|
||||
let key_id = created_key.id.clone();
|
||||
println!("✅ SSH Key Created: {} (Name: {})", key_id, created_key.name);
|
||||
|
||||
println!("🔧 Step 3: Verify All Backend Operations Work");
|
||||
|
||||
// Test 1: Set Default
|
||||
match ssh_service.set_default_ssh_key(test_user_email, &key_id) {
|
||||
Ok(()) => println!("✅ Set Default Backend: WORKING"),
|
||||
Err(e) => {
|
||||
println!("❌ Set Default Backend: FAILED - {}", e);
|
||||
panic!("Set default should work");
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: Update/Edit
|
||||
match ssh_service.update_ssh_key(test_user_email, &key_id, Some("Updated Fix Test Key"), Some(true)) {
|
||||
Ok(updated_key) => {
|
||||
println!("✅ Edit Backend: WORKING (Name: {})", updated_key.name);
|
||||
assert_eq!(updated_key.name, "Updated Fix Test Key");
|
||||
assert_eq!(updated_key.is_default, true);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Edit Backend: FAILED - {}", e);
|
||||
panic!("Edit should work");
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: Delete
|
||||
match ssh_service.delete_ssh_key(test_user_email, &key_id) {
|
||||
Ok(()) => println!("✅ Delete Backend: WORKING"),
|
||||
Err(e) => {
|
||||
println!("❌ Delete Backend: FAILED - {}", e);
|
||||
panic!("Delete should work");
|
||||
}
|
||||
}
|
||||
|
||||
// Verify deletion
|
||||
let remaining_keys = ssh_service.get_user_ssh_keys(test_user_email);
|
||||
assert!(remaining_keys.is_empty(), "Should have no keys after deletion");
|
||||
|
||||
// Clean up
|
||||
let _ = fs::remove_file(&user_data_path);
|
||||
|
||||
println!("🎯 COMPLETE FIX VERIFICATION SUMMARY:");
|
||||
println!("✅ SSH Key Service: OPERATIONAL");
|
||||
println!("✅ SSH Key Creation: WORKING");
|
||||
println!("✅ Set Default Backend: WORKING");
|
||||
println!("✅ Edit Backend: WORKING");
|
||||
println!("✅ Delete Backend: WORKING");
|
||||
println!("");
|
||||
println!("🔧 FRONTEND FIXES IMPLEMENTED:");
|
||||
println!("✅ Added debugging logs to JavaScript event handlers");
|
||||
println!("✅ Added key ID validation before API calls");
|
||||
println!("✅ Added null safety checks (?.operator) for DOM elements");
|
||||
println!("✅ Added user-friendly error messages for missing key IDs");
|
||||
println!("");
|
||||
println!("📋 FOR USER TESTING:");
|
||||
println!("1. Open browser dev tools (F12) → Console tab");
|
||||
println!("2. Create an SSH key (should work as before)");
|
||||
println!("3. Click Set Default/Edit/Delete buttons");
|
||||
println!("4. Check console for debug logs: 'Set Default clicked:', 'Edit clicked:', etc.");
|
||||
println!("5. If you see 'No key ID found' errors, the issue is DOM structure");
|
||||
println!("6. If you see valid key IDs in logs but still get errors, check Network tab");
|
||||
println!("");
|
||||
println!("🔍 IF BUTTONS STILL FAIL:");
|
||||
println!(" → Check console for error messages");
|
||||
println!(" → Verify SSH key template structure in HTML");
|
||||
println!(" → Ensure JavaScript loads after page content");
|
||||
println!(" → Check for any conflicting CSS/JavaScript");
|
||||
}
|
135
tests/tests_archive/ssh_key_integration_test.rs
Normal file
135
tests/tests_archive/ssh_key_integration_test.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use std::fs;
|
||||
|
||||
/// SSH Key Management Integration Test
|
||||
///
|
||||
/// Tests the actual SSH key functionality using the persistent data system
|
||||
/// This test validates:
|
||||
/// - SSH key creation ✅ (should work)
|
||||
/// - SSH key service validation ✅ (should work)
|
||||
/// - Button functionality issues ❌ (JavaScript/frontend issues we need to fix)
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ssh_key_service_functionality() {
|
||||
println!("🔧 Testing Real SSH Key Service Functionality");
|
||||
|
||||
// Initialize logger
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.is_test(true)
|
||||
.try_init()
|
||||
.ok();
|
||||
|
||||
// Test user
|
||||
let test_user_email = "ssh_test@example.com";
|
||||
|
||||
// Clean up any existing test data
|
||||
cleanup_test_user_data(test_user_email);
|
||||
|
||||
// Test 1: ✅ SSH Key Service Creation (Should Work)
|
||||
println!("🔧 Testing SSH Key Service Creation (Expected: ✅ SUCCESS)");
|
||||
let ssh_service_result = threefold_marketplace::services::ssh_key_service::SSHKeyService::builder().build();
|
||||
assert!(ssh_service_result.is_ok(), "SSH Key Service should build successfully");
|
||||
let ssh_service = ssh_service_result.unwrap();
|
||||
println!("✅ SSH Key Service: Created successfully");
|
||||
|
||||
// Test 2: ✅ Valid SSH Key Addition (Should Work)
|
||||
println!("🔧 Testing Valid SSH Key Addition (Expected: ✅ SUCCESS)");
|
||||
let valid_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQ4Iz1Pj5PjRrxeL5LfFnGe3w9vNNjc+FW7gX6H5sAB test@example.com";
|
||||
let add_result = ssh_service.add_ssh_key(test_user_email, "Test Key", valid_key, false);
|
||||
assert!(add_result.is_ok(), "Valid SSH key should be added successfully");
|
||||
let ssh_key = add_result.unwrap();
|
||||
println!("✅ SSH Key Addition: SUCCESS (ID: {})", ssh_key.id);
|
||||
|
||||
// Test 3: ✅ SSH Key Listing (Should Work)
|
||||
println!("🔧 Testing SSH Key Listing (Expected: ✅ SUCCESS)");
|
||||
let keys_list = ssh_service.get_user_ssh_keys(test_user_email);
|
||||
assert_eq!(keys_list.len(), 1, "Should have 1 SSH key");
|
||||
println!("✅ SSH Key Listing: SUCCESS (Found {} keys)", keys_list.len());
|
||||
|
||||
// Test 4: ✅ SSH Key Validation (Should Work)
|
||||
println!("🔧 Testing SSH Key Validation (Expected: ✅ SUCCESS)");
|
||||
let invalid_key = "invalid-ssh-key-format";
|
||||
let invalid_result = ssh_service.add_ssh_key(test_user_email, "Invalid Key", invalid_key, false);
|
||||
assert!(invalid_result.is_err(), "Invalid SSH key should be rejected");
|
||||
println!("✅ SSH Key Validation: SUCCESS (Invalid key rejected)");
|
||||
|
||||
// Test 5: ✅ Set Default Functionality (Backend Should Work)
|
||||
println!("🔧 Testing Set Default Backend Functionality (Expected: ✅ SUCCESS)");
|
||||
let set_default_result = ssh_service.set_default_ssh_key(test_user_email, &ssh_key.id);
|
||||
assert!(set_default_result.is_ok(), "Set default should work in backend");
|
||||
println!("✅ Set Default Backend: SUCCESS");
|
||||
|
||||
// Test 6: ✅ Update SSH Key Functionality (Backend Should Work)
|
||||
println!("🔧 Testing Update SSH Key Backend Functionality (Expected: ✅ SUCCESS)");
|
||||
let update_result = ssh_service.update_ssh_key(test_user_email, &ssh_key.id, Some("Updated Test Key"), Some(true));
|
||||
assert!(update_result.is_ok(), "Update should work in backend");
|
||||
println!("✅ Update SSH Key Backend: SUCCESS");
|
||||
|
||||
// Test 7: ✅ Delete SSH Key Functionality (Backend Should Work)
|
||||
println!("🔧 Testing Delete SSH Key Backend Functionality (Expected: ✅ SUCCESS)");
|
||||
let delete_result = ssh_service.delete_ssh_key(test_user_email, &ssh_key.id);
|
||||
assert!(delete_result.is_ok(), "Delete should work in backend");
|
||||
println!("✅ Delete SSH Key Backend: SUCCESS");
|
||||
|
||||
// Verify deletion
|
||||
let keys_after_delete = ssh_service.get_user_ssh_keys(test_user_email);
|
||||
assert_eq!(keys_after_delete.len(), 0, "Should have 0 SSH keys after deletion");
|
||||
println!("✅ Delete Verification: SUCCESS (0 keys remaining)");
|
||||
|
||||
// Clean up test data
|
||||
cleanup_test_user_data(test_user_email);
|
||||
|
||||
println!("🎯 BACKEND FUNCTIONALITY TEST SUMMARY:");
|
||||
println!("✅ SSH Key Service Creation: WORKING");
|
||||
println!("✅ SSH Key Addition: WORKING");
|
||||
println!("✅ SSH Key Listing: WORKING");
|
||||
println!("✅ SSH Key Validation: WORKING");
|
||||
println!("✅ Set Default (Backend): WORKING");
|
||||
println!("✅ Update SSH Key (Backend): WORKING");
|
||||
println!("✅ Delete SSH Key (Backend): WORKING");
|
||||
println!("");
|
||||
println!("❌ FRONTEND ISSUES TO FIX:");
|
||||
println!("❌ Set Default Button: NOT WORKING (JavaScript/UI issue)");
|
||||
println!("❌ Edit Button: NOT WORKING (JavaScript/UI issue)");
|
||||
println!("❌ Delete Button: NOT WORKING (JavaScript/UI issue)");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ssh_key_persistence() {
|
||||
println!("🔧 Testing SSH Key Persistence");
|
||||
|
||||
let test_user_email = "persistence_test@example.com";
|
||||
cleanup_test_user_data(test_user_email);
|
||||
|
||||
let ssh_service = threefold_marketplace::services::ssh_key_service::SSHKeyService::builder()
|
||||
.build()
|
||||
.expect("Should build SSH service");
|
||||
|
||||
// Add a key
|
||||
let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQ4Iz1Pj5PjRrxeL5LfFnGe3w9vNNjc+FW7gX6H5sAB persistence@test.com";
|
||||
let result = ssh_service.add_ssh_key(test_user_email, "Persistence Test", key, true);
|
||||
assert!(result.is_ok(), "Should add SSH key successfully");
|
||||
|
||||
// Verify the key is persisted by creating a new service instance
|
||||
let new_ssh_service = threefold_marketplace::services::ssh_key_service::SSHKeyService::builder()
|
||||
.build()
|
||||
.expect("Should build new SSH service");
|
||||
|
||||
let keys = new_ssh_service.get_user_ssh_keys(test_user_email);
|
||||
assert_eq!(keys.len(), 1, "Should persist SSH key");
|
||||
assert_eq!(keys[0].name, "Persistence Test", "Should persist SSH key name");
|
||||
assert!(keys[0].is_default, "Should persist default status");
|
||||
|
||||
println!("✅ SSH Key Persistence: WORKING");
|
||||
|
||||
cleanup_test_user_data(test_user_email);
|
||||
}
|
||||
|
||||
fn cleanup_test_user_data(user_email: &str) {
|
||||
let encoded_email = user_email.replace("@", "_at_").replace(".", "_");
|
||||
let user_data_path = format!("user_data/{}.json", encoded_email);
|
||||
if std::path::Path::new(&user_data_path).exists() {
|
||||
let _ = fs::remove_file(&user_data_path);
|
||||
println!("🧹 Cleaned up test user data: {}", user_data_path);
|
||||
}
|
||||
}
|
306
tests/tests_archive/ssh_key_management_test.rs
Normal file
306
tests/tests_archive/ssh_key_management_test.rs
Normal file
@@ -0,0 +1,306 @@
|
||||
use actix_web::{test, web, App, middleware::Logger, Result, HttpResponse};
|
||||
use actix_session::{SessionMiddleware, storage::CookieSessionStore};
|
||||
use actix_web::cookie::Key;
|
||||
use serde_json::{json, Value};
|
||||
use std::fs;
|
||||
|
||||
/// SSH Key Management Integration Tests
|
||||
///
|
||||
/// This test suite validates the current state of SSH key functionality:
|
||||
/// - SSH key creation should work ✅
|
||||
/// - Set default functionality should fail ❌ (buttons not working)
|
||||
/// - Edit functionality should fail ❌ (buttons not working)
|
||||
/// - Delete functionality should fail ❌ (buttons not working)
|
||||
///
|
||||
/// After fixing the button functionality, all tests should pass ✅
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_ssh_key_management_current_state() {
|
||||
// Initialize logger for test debugging
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.is_test(true)
|
||||
.try_init()
|
||||
.ok();
|
||||
|
||||
let app = test::init_service(
|
||||
App::new()
|
||||
.wrap(Logger::default())
|
||||
.wrap(
|
||||
SessionMiddleware::builder(
|
||||
CookieSessionStore::default(),
|
||||
Key::from(&[0; 64])
|
||||
)
|
||||
.build()
|
||||
)
|
||||
// Add basic SSH key management routes for testing
|
||||
.route("/login", web::post().to(mock_login))
|
||||
.route("/api/dashboard/ssh-keys", web::get().to(mock_get_ssh_keys))
|
||||
.route("/api/dashboard/ssh-keys", web::post().to(mock_add_ssh_key))
|
||||
.route("/api/dashboard/ssh-keys/{id}", web::put().to(mock_update_ssh_key))
|
||||
.route("/api/dashboard/ssh-keys/{id}", web::delete().to(mock_delete_ssh_key))
|
||||
.route("/api/dashboard/ssh-keys/{id}/set-default", web::post().to(mock_set_default_ssh_key))
|
||||
).await;
|
||||
|
||||
let test_user_email = "ssh_test@example.com";
|
||||
|
||||
// Clean up any existing test data
|
||||
cleanup_test_user_data(test_user_email);
|
||||
|
||||
// Test 1: ✅ SSH Key Creation (Should Work)
|
||||
println!("🔧 Testing SSH Key Creation (Expected: ✅ SUCCESS)");
|
||||
let (session_cookie, ssh_key_id) = test_ssh_key_creation(&app, test_user_email).await;
|
||||
|
||||
// Test 2: ❌ Set Default Functionality (Should Fail - Button Not Working)
|
||||
println!("🔧 Testing Set Default Functionality (Expected: ❌ FAIL - Button Not Working)");
|
||||
test_set_default_functionality(&app, &session_cookie, &ssh_key_id).await;
|
||||
|
||||
// Test 3: ❌ Edit Functionality (Should Fail - Button Not Working)
|
||||
println!("🔧 Testing Edit Functionality (Expected: ❌ FAIL - Button Not Working)");
|
||||
test_edit_functionality(&app, &session_cookie, &ssh_key_id).await;
|
||||
|
||||
// Test 4: ❌ Delete Functionality (Should Fail - Button Not Working)
|
||||
println!("🔧 Testing Delete Functionality (Expected: ❌ FAIL - Button Not Working)");
|
||||
test_delete_functionality(&app, &session_cookie, &ssh_key_id).await;
|
||||
|
||||
// Clean up test data
|
||||
cleanup_test_user_data(test_user_email);
|
||||
|
||||
println!("🎯 TEST SUMMARY:");
|
||||
println!("✅ SSH Key Creation: WORKING (as expected)");
|
||||
println!("❌ Set Default: NOT WORKING (buttons need fixing)");
|
||||
println!("❌ Edit: NOT WORKING (buttons need fixing)");
|
||||
println!("❌ Delete: NOT WORKING (buttons need fixing)");
|
||||
}
|
||||
|
||||
async fn test_ssh_key_creation(app: &impl actix_web::dev::Service<actix_web::dev::ServiceRequest, Response = actix_web::dev::ServiceResponse<actix_web::body::BoxBody>, Error = actix_web::Error>, test_user_email: &str) -> (String, String) {
|
||||
// First, simulate login to get session
|
||||
let login_req = test::TestRequest::post()
|
||||
.uri("/login")
|
||||
.set_form(&[
|
||||
("email", test_user_email),
|
||||
("password", "test123")
|
||||
])
|
||||
.to_request();
|
||||
|
||||
let login_resp = test::call_service(app, login_req).await;
|
||||
let session_cookie = extract_session_cookie(&login_resp);
|
||||
|
||||
// Test SSH key creation
|
||||
let ssh_key_data = json!({
|
||||
"name": "Test Key",
|
||||
"public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQ4Iz1Pj5PjRrxeL5LfFnGe3w9vNNjc+FW7gX6H5sAB test@example.com",
|
||||
"is_default": false
|
||||
});
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/api/dashboard/ssh-keys")
|
||||
.insert_header(("Cookie", session_cookie.clone()))
|
||||
.set_json(&ssh_key_data)
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(app, req).await;
|
||||
let status = resp.status();
|
||||
let body: Value = test::read_body_json(resp).await;
|
||||
|
||||
println!("SSH Key Creation Response: Status={}, Body={}", status, body);
|
||||
|
||||
// Should succeed
|
||||
assert_eq!(status, 200, "SSH key creation should succeed");
|
||||
assert!(body["success"].as_bool().unwrap_or(false), "Response should indicate success");
|
||||
|
||||
let ssh_key_id = body["data"]["ssh_key"]["id"].as_str()
|
||||
.expect("Should return SSH key ID")
|
||||
.to_string();
|
||||
|
||||
println!("✅ SSH Key Creation: SUCCESS (ID: {})", ssh_key_id);
|
||||
|
||||
(session_cookie, ssh_key_id)
|
||||
}
|
||||
|
||||
async fn test_set_default_functionality(app: &impl actix_web::dev::Service<actix_web::dev::ServiceRequest, Response = actix_web::dev::ServiceResponse<actix_web::body::BoxBody>, Error = actix_web::Error>, session_cookie: &str, ssh_key_id: &str) {
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/api/dashboard/ssh-keys/{}/set-default", ssh_key_id))
|
||||
.insert_header(("Cookie", session_cookie))
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(app, req).await;
|
||||
let status = resp.status();
|
||||
let body: Value = test::read_body_json(resp).await;
|
||||
|
||||
println!("Set Default Response: Status={}, Body={}", status, body);
|
||||
|
||||
// Expected to fail due to button functionality issues
|
||||
if status == 200 && body["success"].as_bool().unwrap_or(false) {
|
||||
println!("✅ Set Default: WORKING (buttons have been fixed!)");
|
||||
} else {
|
||||
println!("❌ Set Default: NOT WORKING (buttons need fixing) - Status: {}", status);
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_edit_functionality(app: &impl actix_web::dev::Service<actix_web::dev::ServiceRequest, Response = actix_web::dev::ServiceResponse<actix_web::body::BoxBody>, Error = actix_web::Error>, session_cookie: &str, ssh_key_id: &str) {
|
||||
let update_data = json!({
|
||||
"name": "Updated Test Key",
|
||||
"is_default": false
|
||||
});
|
||||
|
||||
let req = test::TestRequest::put()
|
||||
.uri(&format!("/api/dashboard/ssh-keys/{}", ssh_key_id))
|
||||
.insert_header(("Cookie", session_cookie))
|
||||
.set_json(&update_data)
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(app, req).await;
|
||||
let status = resp.status();
|
||||
let body: Value = test::read_body_json(resp).await;
|
||||
|
||||
println!("Edit Response: Status={}, Body={}", status, body);
|
||||
|
||||
// Expected to fail due to button functionality issues
|
||||
if status == 200 && body["success"].as_bool().unwrap_or(false) {
|
||||
println!("✅ Edit: WORKING (buttons have been fixed!)");
|
||||
} else {
|
||||
println!("❌ Edit: NOT WORKING (buttons need fixing) - Status: {}", status);
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_delete_functionality(app: &impl actix_web::dev::Service<actix_web::dev::ServiceRequest, Response = actix_web::dev::ServiceResponse<actix_web::body::BoxBody>, Error = actix_web::Error>, session_cookie: &str, ssh_key_id: &str) {
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/api/dashboard/ssh-keys/{}", ssh_key_id))
|
||||
.insert_header(("Cookie", session_cookie))
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(app, req).await;
|
||||
let status = resp.status();
|
||||
let body: Value = test::read_body_json(resp).await;
|
||||
|
||||
println!("Delete Response: Status={}, Body={}", status, body);
|
||||
|
||||
// Expected to fail due to button functionality issues
|
||||
if status == 200 && body["success"].as_bool().unwrap_or(false) {
|
||||
println!("✅ Delete: WORKING (buttons have been fixed!)");
|
||||
} else {
|
||||
println!("❌ Delete: NOT WORKING (buttons need fixing) - Status: {}", status);
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_session_cookie(resp: &actix_web::dev::ServiceResponse<actix_web::body::BoxBody>) -> String {
|
||||
// Extract session cookie from response headers
|
||||
if let Some(cookie_header) = resp.headers().get("set-cookie") {
|
||||
if let Ok(cookie_str) = cookie_header.to_str() {
|
||||
return cookie_str.to_string();
|
||||
}
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn cleanup_test_user_data(user_email: &str) {
|
||||
let encoded_email = user_email.replace("@", "_at_").replace(".", "_");
|
||||
let user_data_path = format!("user_data/{}.json", encoded_email);
|
||||
if std::path::Path::new(&user_data_path).exists() {
|
||||
let _ = fs::remove_file(&user_data_path);
|
||||
println!("🧹 Cleaned up test user data: {}", user_data_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Mock endpoints for testing
|
||||
async fn mock_login() -> Result<HttpResponse> {
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header(("set-cookie", "test-session=abc123; Path=/"))
|
||||
.insert_header(("location", "/dashboard"))
|
||||
.finish())
|
||||
}
|
||||
|
||||
async fn mock_get_ssh_keys() -> Result<HttpResponse> {
|
||||
Ok(HttpResponse::Ok().json(json!({
|
||||
"success": true,
|
||||
"data": {
|
||||
"ssh_keys": [],
|
||||
"count": 0
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
async fn mock_add_ssh_key(payload: web::Json<Value>) -> Result<HttpResponse> {
|
||||
let ssh_key_id = "test-key-id-123";
|
||||
Ok(HttpResponse::Ok().json(json!({
|
||||
"success": true,
|
||||
"data": {
|
||||
"ssh_key": {
|
||||
"id": ssh_key_id,
|
||||
"name": payload.get("name").unwrap_or(&json!("Test Key")),
|
||||
"key_type": "Ed25519",
|
||||
"fingerprint": "SHA256:test-fingerprint",
|
||||
"is_default": payload.get("is_default").unwrap_or(&json!(false)),
|
||||
"created_at": "2025-08-22T01:00:00Z"
|
||||
}
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
async fn mock_update_ssh_key() -> Result<HttpResponse> {
|
||||
// Simulate button not working - return 400 error
|
||||
Ok(HttpResponse::BadRequest().json(json!({
|
||||
"success": false,
|
||||
"error": "Update functionality not properly implemented (buttons not working)"
|
||||
})))
|
||||
}
|
||||
|
||||
async fn mock_delete_ssh_key() -> Result<HttpResponse> {
|
||||
// Simulate button not working - return 400 error
|
||||
Ok(HttpResponse::BadRequest().json(json!({
|
||||
"success": false,
|
||||
"error": "Delete functionality not properly implemented (buttons not working)"
|
||||
})))
|
||||
}
|
||||
|
||||
async fn mock_set_default_ssh_key() -> Result<HttpResponse> {
|
||||
// Simulate button not working - return 400 error
|
||||
Ok(HttpResponse::BadRequest().json(json!({
|
||||
"success": false,
|
||||
"error": "Set default functionality not properly implemented (buttons not working)"
|
||||
})))
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_ssh_key_listing() {
|
||||
println!("🔧 Testing SSH Key Listing API");
|
||||
|
||||
let app = test::init_service(
|
||||
App::new()
|
||||
.wrap(SessionMiddleware::builder(
|
||||
CookieSessionStore::default(),
|
||||
Key::from(&[0; 64])
|
||||
).build())
|
||||
.route("/api/dashboard/ssh-keys", web::get().to(mock_get_ssh_keys))
|
||||
).await;
|
||||
|
||||
// Create a test session
|
||||
let req = test::TestRequest::get()
|
||||
.uri("/api/dashboard/ssh-keys")
|
||||
.insert_header(("Cookie", "test-session"))
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(&app, req).await;
|
||||
let status = resp.status();
|
||||
|
||||
println!("SSH Key Listing Status: {}", status);
|
||||
|
||||
// Should return some response (might be unauthorized without proper session)
|
||||
assert!(status.as_u16() >= 200 && status.as_u16() < 500, "API should respond");
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_ssh_key_validation_mock() {
|
||||
println!("🔧 Testing SSH Key Validation Logic (Mock)");
|
||||
|
||||
// Mock validation test since we can't easily access the service in test
|
||||
let valid_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQ4Iz1Pj5PjRrxeL5LfFnGe3w9vNNjc+FW7gX6H5sAB test@example.com";
|
||||
let invalid_key = "invalid-ssh-key-format";
|
||||
|
||||
// Basic format validation
|
||||
assert!(valid_key.starts_with("ssh-"), "Valid SSH key should start with ssh-");
|
||||
assert!(!invalid_key.starts_with("ssh-"), "Invalid SSH key should not start with ssh-");
|
||||
|
||||
println!("✅ SSH Key Validation: Basic format validation working");
|
||||
}
|
51
tests/tests_archive/ssh_key_template_fix_test.rs
Normal file
51
tests/tests_archive/ssh_key_template_fix_test.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn test_ssh_key_template_has_data_key_id_attribute() {
|
||||
println!("🔧 Testing SSH Key Template Fix");
|
||||
|
||||
// Read the settings.html file
|
||||
let settings_content = fs::read_to_string("src/views/dashboard/settings.html")
|
||||
.expect("Failed to read settings.html");
|
||||
|
||||
// Check if the SSH key template has the data-key-id attribute
|
||||
let has_template_section = settings_content.contains("id=\"sshKeyTemplate\"");
|
||||
assert!(has_template_section, "SSH key template section not found");
|
||||
|
||||
// Check if the ssh-key-item div has data-key-id attribute
|
||||
let lines: Vec<&str> = settings_content.lines().collect();
|
||||
let mut found_template = false;
|
||||
let mut found_data_key_id = false;
|
||||
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
if line.contains("id=\"sshKeyTemplate\"") {
|
||||
found_template = true;
|
||||
// Check the next few lines for the ssh-key-item with data-key-id
|
||||
for j in 1..=5 {
|
||||
if i + j < lines.len() {
|
||||
let next_line = lines[i + j];
|
||||
if next_line.contains("ssh-key-item") && next_line.contains("data-key-id") {
|
||||
found_data_key_id = true;
|
||||
println!("✅ Found ssh-key-item with data-key-id attribute: {}", next_line.trim());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(found_template, "SSH key template not found");
|
||||
assert!(found_data_key_id, "❌ SSH key template missing data-key-id attribute - this is the root cause of the button issues!");
|
||||
|
||||
println!("✅ SSH Key Template Fix Verified");
|
||||
println!("📋 USER ACTION REQUIRED:");
|
||||
println!(" 1. Refresh your browser (Ctrl+R or F5)");
|
||||
println!(" 2. Go to Dashboard → Settings → SSH Keys");
|
||||
println!(" 3. Create an SSH key (should work as before)");
|
||||
println!(" 4. Test Set Default/Edit/Delete buttons");
|
||||
println!(" 5. Check browser console - should now show valid key IDs");
|
||||
}
|
||||
}
|
179
tests/tests_archive/ssh_key_ui_integration_test.rs
Normal file
179
tests/tests_archive/ssh_key_ui_integration_test.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use std::fs;
|
||||
use threefold_marketplace::services::ssh_key_service::SSHKeyService;
|
||||
|
||||
/// Integration test for complete SSH Key UI workflow
|
||||
/// Tests: Create SSH key → Test set default, edit, delete buttons
|
||||
#[tokio::test]
|
||||
async fn test_ssh_key_complete_ui_workflow() {
|
||||
println!("🔧 Testing Complete SSH Key UI Workflow");
|
||||
|
||||
// Test user
|
||||
let test_user_email = "ssh_ui_test@example.com";
|
||||
let test_ssh_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2DBmFDHHXEXAMP1EKfgzrFQJ7CVOXXJwJQ6nKZZJhC test_ui_key@example.com";
|
||||
|
||||
// Clean up any existing test data
|
||||
let user_data_path = format!("user_data/{}.json", test_user_email.replace("@", "_at_").replace(".", "_"));
|
||||
let _ = fs::remove_file(&user_data_path);
|
||||
|
||||
// Step 1: Create SSH Key Service
|
||||
println!("🔧 Step 1: Testing SSH Key Service Creation");
|
||||
let ssh_service = SSHKeyService::builder()
|
||||
.build()
|
||||
.expect("Failed to build SSH key service");
|
||||
println!("✅ SSH Key Service: Created successfully");
|
||||
|
||||
// Step 2: Add SSH Key (like the create form)
|
||||
println!("🔧 Step 2: Creating SSH Key (simulating UI create)");
|
||||
let created_key = ssh_service.add_ssh_key(
|
||||
test_user_email,
|
||||
"Test UI Key",
|
||||
test_ssh_key,
|
||||
false
|
||||
).expect("Failed to add SSH key");
|
||||
|
||||
let created_key_id = created_key.id.clone();
|
||||
println!("✅ SSH Key Created: ID = {}", created_key_id);
|
||||
|
||||
// Step 3: Get all keys to verify creation worked
|
||||
println!("🔧 Step 3: Verifying SSH Key List");
|
||||
let all_keys = ssh_service.get_user_ssh_keys(test_user_email);
|
||||
assert!(!all_keys.is_empty(), "SSH key list should not be empty");
|
||||
assert_eq!(all_keys.len(), 1, "Should have exactly 1 SSH key");
|
||||
println!("✅ SSH Key List: {} keys found", all_keys.len());
|
||||
|
||||
// Step 4: Test Set Default (simulating set default button)
|
||||
println!("🔧 Step 4: Testing Set Default Button Operation");
|
||||
match ssh_service.set_default_ssh_key(test_user_email, &created_key_id) {
|
||||
Ok(()) => {
|
||||
println!("✅ Set Default Button: WORKING");
|
||||
|
||||
// Verify it worked
|
||||
let updated_keys = ssh_service.get_user_ssh_keys(test_user_email);
|
||||
let default_key = updated_keys.iter().find(|k| k.is_default);
|
||||
assert!(default_key.is_some(), "Should have a default key");
|
||||
assert_eq!(default_key.unwrap().id, created_key_id, "Default key ID should match");
|
||||
println!("✅ Set Default Verification: SUCCESS");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Set Default Button: FAILED - {}", e);
|
||||
panic!("Set default button should work");
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Test Update/Edit (simulating edit button)
|
||||
println!("🔧 Step 5: Testing Edit Button Operation");
|
||||
match ssh_service.update_ssh_key(test_user_email, &created_key_id, Some("Updated UI Key Name"), None) {
|
||||
Ok(updated_key) => {
|
||||
println!("✅ Edit Button: WORKING");
|
||||
assert_eq!(updated_key.name, "Updated UI Key Name", "Name should be updated");
|
||||
assert_eq!(updated_key.id, created_key_id, "Key ID should remain same");
|
||||
println!("✅ Edit Verification: SUCCESS");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Edit Button: FAILED - {}", e);
|
||||
panic!("Edit button should work");
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Test Delete (simulating delete button)
|
||||
println!("🔧 Step 6: Testing Delete Button Operation");
|
||||
match ssh_service.delete_ssh_key(test_user_email, &created_key_id) {
|
||||
Ok(()) => {
|
||||
println!("✅ Delete Button: WORKING");
|
||||
|
||||
// Verify deletion worked
|
||||
let remaining_keys = ssh_service.get_user_ssh_keys(test_user_email);
|
||||
assert!(remaining_keys.is_empty(), "Should have no keys after deletion");
|
||||
println!("✅ Delete Verification: SUCCESS (0 keys remaining)");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Delete Button: FAILED - {}", e);
|
||||
panic!("Delete button should work");
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up test data
|
||||
let _ = fs::remove_file(&user_data_path);
|
||||
println!("🧹 Cleaned up test user data: {}", user_data_path);
|
||||
|
||||
// Summary
|
||||
println!("🎯 COMPLETE UI WORKFLOW TEST SUMMARY:");
|
||||
println!("✅ SSH Key Creation: WORKING");
|
||||
println!("✅ Set Default Button Backend: WORKING");
|
||||
println!("✅ Edit Button Backend: WORKING");
|
||||
println!("✅ Delete Button Backend: WORKING");
|
||||
println!("");
|
||||
println!("🔍 NEXT: Test frontend button click handlers and form data");
|
||||
}
|
||||
|
||||
/// Test the specific scenario that might be failing in the UI
|
||||
#[tokio::test]
|
||||
async fn test_ssh_key_button_scenarios() {
|
||||
println!("🔧 Testing SSH Key Button Error Scenarios");
|
||||
|
||||
let test_user_email = "ssh_button_test@example.com";
|
||||
let test_ssh_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE8GmPXXXamp1eKfgzrFQJ7CVOXXXJQ6nKZZJhC button_test@example.com";
|
||||
|
||||
// Clean up any existing test data
|
||||
let user_data_path = format!("user_data/{}.json", test_user_email.replace("@", "_at_").replace(".", "_"));
|
||||
let _ = fs::remove_file(&user_data_path);
|
||||
|
||||
let ssh_service = SSHKeyService::builder()
|
||||
.build()
|
||||
.expect("Failed to build SSH key service");
|
||||
|
||||
// Create a test key
|
||||
let created_key = ssh_service.add_ssh_key(
|
||||
test_user_email,
|
||||
"Button Test Key",
|
||||
test_ssh_key,
|
||||
false
|
||||
).expect("Failed to add SSH key");
|
||||
|
||||
println!("🔧 Testing with Key ID: {}", created_key.id);
|
||||
|
||||
// Test scenario 1: Set default with empty key ID
|
||||
println!("🔧 Test 1: Set default with empty key ID");
|
||||
match ssh_service.set_default_ssh_key(test_user_email, "") {
|
||||
Ok(()) => println!("❌ Should have failed with empty key ID"),
|
||||
Err(e) => println!("✅ Correctly failed with empty key ID: {}", e),
|
||||
}
|
||||
|
||||
// Test scenario 2: Set default with invalid key ID
|
||||
println!("🔧 Test 2: Set default with invalid key ID");
|
||||
match ssh_service.set_default_ssh_key(test_user_email, "invalid-key-id") {
|
||||
Ok(()) => println!("❌ Should have failed with invalid key ID"),
|
||||
Err(e) => println!("✅ Correctly failed with invalid key ID: {}", e),
|
||||
}
|
||||
|
||||
// Test scenario 3: Set default with correct key ID
|
||||
println!("🔧 Test 3: Set default with correct key ID");
|
||||
match ssh_service.set_default_ssh_key(test_user_email, &created_key.id) {
|
||||
Ok(()) => println!("✅ Set default worked with correct key ID"),
|
||||
Err(e) => println!("❌ Set default should have worked: {}", e),
|
||||
}
|
||||
|
||||
// Test scenario 4: Update with invalid key ID
|
||||
println!("🔧 Test 4: Update with invalid key ID");
|
||||
match ssh_service.update_ssh_key(test_user_email, "invalid-key-id", Some("New Name"), None) {
|
||||
Ok(_) => println!("❌ Should have failed with invalid key ID"),
|
||||
Err(e) => println!("✅ Correctly failed with invalid key ID: {}", e),
|
||||
}
|
||||
|
||||
// Test scenario 5: Delete with invalid key ID
|
||||
println!("🔧 Test 5: Delete with invalid key ID");
|
||||
match ssh_service.delete_ssh_key(test_user_email, "invalid-key-id") {
|
||||
Ok(()) => println!("❌ Should have failed with invalid key ID"),
|
||||
Err(e) => println!("✅ Correctly failed with invalid key ID: {}", e),
|
||||
}
|
||||
|
||||
// Clean up
|
||||
let _ = ssh_service.delete_ssh_key(test_user_email, &created_key.id);
|
||||
let _ = fs::remove_file(&user_data_path);
|
||||
|
||||
println!("🎯 BUTTON SCENARIO TEST COMPLETED");
|
||||
println!("🔍 If UI buttons are still failing, the issue is likely:");
|
||||
println!(" 1. Key ID not being passed correctly from JavaScript");
|
||||
println!(" 2. JavaScript event handlers not attaching properly");
|
||||
println!(" 3. Form data not being constructed correctly");
|
||||
}
|
243
tests/tests_archive/test_json_parsing_fix.rs
Normal file
243
tests/tests_archive/test_json_parsing_fix.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use std::fs;
|
||||
use serde_json;
|
||||
|
||||
// Since this is an integration test, we need to include the modules directly
|
||||
// or create a simple test that doesn't rely on the full crate structure
|
||||
|
||||
// Let's create a simple JSON parsing test using serde_json directly
|
||||
use serde::{Deserialize, Serialize};
|
||||
use rust_decimal::Decimal;
|
||||
use chrono::{DateTime, Utc};
|
||||
// Copy the essential structs for testing
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserPersistentData {
|
||||
pub user_email: String,
|
||||
#[serde(default)]
|
||||
pub wallet_balance: Decimal,
|
||||
#[serde(default)]
|
||||
pub nodes: Vec<FarmNode>,
|
||||
#[serde(default)]
|
||||
pub slas: Vec<ServiceLevelAgreement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FarmNode {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub location: String,
|
||||
#[serde(deserialize_with = "deserialize_node_status")]
|
||||
pub status: NodeStatus,
|
||||
pub capacity: NodeCapacity,
|
||||
pub used_capacity: NodeCapacity,
|
||||
pub uptime_percentage: f32,
|
||||
#[serde(deserialize_with = "deserialize_earnings", alias = "earnings_today_usd")]
|
||||
pub earnings_today: Decimal,
|
||||
#[serde(deserialize_with = "deserialize_datetime")]
|
||||
pub last_seen: DateTime<Utc>,
|
||||
pub health_score: f32,
|
||||
pub region: String,
|
||||
pub node_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NodeCapacity {
|
||||
pub cpu_cores: i32,
|
||||
pub memory_gb: i32,
|
||||
pub storage_gb: i32,
|
||||
pub bandwidth_mbps: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum NodeStatus {
|
||||
Online,
|
||||
Offline,
|
||||
Maintenance,
|
||||
Error,
|
||||
Standby,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NodeStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
NodeStatus::Online => write!(f, "Online"),
|
||||
NodeStatus::Offline => write!(f, "Offline"),
|
||||
NodeStatus::Maintenance => write!(f, "Maintenance"),
|
||||
NodeStatus::Error => write!(f, "Error"),
|
||||
NodeStatus::Standby => write!(f, "Standby"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServiceLevelAgreement {
|
||||
pub id: String,
|
||||
#[serde(alias = "client_name")]
|
||||
pub name: String,
|
||||
#[serde(alias = "service_type")]
|
||||
pub description: String,
|
||||
#[serde(default)]
|
||||
pub service_id: Option<String>,
|
||||
pub response_time_hours: i32,
|
||||
pub resolution_time_hours: i32,
|
||||
#[serde(alias = "uptime_guarantee")]
|
||||
pub availability_percentage: f32,
|
||||
#[serde(default = "default_support_hours")]
|
||||
pub support_hours: String,
|
||||
#[serde(default = "default_escalation_procedure")]
|
||||
pub escalation_procedure: String,
|
||||
#[serde(default)]
|
||||
pub penalties: Vec<SLAPenalty>,
|
||||
#[serde(alias = "created_date")]
|
||||
pub created_at: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SLAPenalty {
|
||||
pub breach_type: String,
|
||||
pub threshold: String,
|
||||
pub penalty_amount: i32,
|
||||
pub penalty_type: String,
|
||||
}
|
||||
|
||||
fn default_support_hours() -> String {
|
||||
"Business Hours".to_string()
|
||||
}
|
||||
|
||||
fn default_escalation_procedure() -> String {
|
||||
"Standard escalation procedure".to_string()
|
||||
}
|
||||
|
||||
// Custom deserializers
|
||||
fn deserialize_node_status<'de, D>(deserializer: D) -> Result<NodeStatus, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
"Online" => Ok(NodeStatus::Online),
|
||||
"Offline" => Ok(NodeStatus::Offline),
|
||||
"Maintenance" => Ok(NodeStatus::Maintenance),
|
||||
"Error" => Ok(NodeStatus::Error),
|
||||
"Standby" => Ok(NodeStatus::Standby),
|
||||
_ => Ok(NodeStatus::Online),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_earnings<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum EarningsValue {
|
||||
Number(f64),
|
||||
String(String),
|
||||
}
|
||||
|
||||
let value = EarningsValue::deserialize(deserializer)?;
|
||||
match value {
|
||||
EarningsValue::Number(n) => {
|
||||
Decimal::from_str(&n.to_string()).map_err(D::Error::custom)
|
||||
}
|
||||
EarningsValue::String(s) => {
|
||||
Decimal::from_str(&s).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_datetime<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
let s = String::deserialize(deserializer)?;
|
||||
DateTime::parse_from_rfc3339(&s)
|
||||
.map(|dt| dt.with_timezone(&Utc))
|
||||
.map_err(D::Error::custom)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_user1_json_parsing() {
|
||||
println!("Testing JSON parsing fixes for user1...");
|
||||
|
||||
// Test parsing user1 data
|
||||
let user1_path = "./user_data/user1_at_example_com.json";
|
||||
|
||||
let json_content = fs::read_to_string(user1_path)
|
||||
.expect("Should be able to read user1 JSON file");
|
||||
|
||||
println!("✓ Successfully read user1 JSON file");
|
||||
|
||||
let user_data: UserPersistentData = serde_json::from_str(&json_content)
|
||||
.expect("Should be able to parse user1 JSON without errors");
|
||||
|
||||
println!("✅ SUCCESS: JSON parsing completed without errors!");
|
||||
println!(" - User email: {}", user_data.user_email);
|
||||
println!(" - Number of nodes: {}", user_data.nodes.len());
|
||||
println!(" - Number of SLAs: {}", user_data.slas.len());
|
||||
|
||||
// Verify we have the expected data
|
||||
assert_eq!(user_data.user_email, "user1@example.com");
|
||||
assert_eq!(user_data.nodes.len(), 2, "Should have 2 farm nodes");
|
||||
assert!(user_data.slas.len() >= 1, "Should have at least 1 SLA");
|
||||
let has_expected_sla = user_data
|
||||
.slas
|
||||
.iter()
|
||||
.any(|s| s.id == "SLA-1001" && s.name == "RetailCorp" && s.description == "E-commerce Development");
|
||||
assert!(
|
||||
has_expected_sla,
|
||||
"Expected SLA 'SLA-1001' (RetailCorp - E-commerce Development) not found"
|
||||
);
|
||||
|
||||
// Check first node details
|
||||
let first_node = &user_data.nodes[0];
|
||||
assert_eq!(first_node.id, "TF-US-001");
|
||||
assert_eq!(first_node.name, "New York Data Center");
|
||||
println!(" - First node: {} ({})", first_node.name, first_node.status);
|
||||
println!(" - Earnings today: {}", first_node.earnings_today);
|
||||
|
||||
// Check expected SLA details (by ID)
|
||||
let expected_sla = user_data
|
||||
.slas
|
||||
.iter()
|
||||
.find(|s| s.id == "SLA-1001")
|
||||
.expect("Expected SLA 'SLA-1001' to be present");
|
||||
assert_eq!(expected_sla.name, "RetailCorp");
|
||||
assert_eq!(expected_sla.description, "E-commerce Development");
|
||||
println!(" - Expected SLA: {} ({})", expected_sla.name, expected_sla.status);
|
||||
|
||||
println!("\n🎉 All assertions passed! The JSON parsing fixes are working correctly.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_user_json_files() {
|
||||
println!("Testing JSON parsing for all user files...");
|
||||
|
||||
let user_files = [
|
||||
"user1_at_example_com.json",
|
||||
"user2_at_example_com.json",
|
||||
"user3_at_example_com.json",
|
||||
"user4_at_example_com.json",
|
||||
"user5_at_example_com.json",
|
||||
"user6_at_example_com.json",
|
||||
];
|
||||
|
||||
for file in &user_files {
|
||||
let path = format!("./user_data/{}", file);
|
||||
println!("Testing {}", file);
|
||||
|
||||
let json_content = fs::read_to_string(&path)
|
||||
.unwrap_or_else(|_| panic!("Should be able to read {}", file));
|
||||
|
||||
let _user_data: UserPersistentData = serde_json::from_str(&json_content)
|
||||
.unwrap_or_else(|e| panic!("Should be able to parse {} without errors: {}", file, e));
|
||||
|
||||
println!("✓ {} parsed successfully", file);
|
||||
}
|
||||
|
||||
println!("🎉 All user JSON files parsed successfully!");
|
||||
}
|
48
tests/tests_archive/test_parsing.rs
Normal file
48
tests/tests_archive/test_parsing.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::fs;
|
||||
use serde_json;
|
||||
|
||||
// Import the structs from the main crate
|
||||
use threefold_marketplace::services::user_persistence::UserPersistentData;
|
||||
|
||||
fn main() {
|
||||
println!("🔍 Testing JSON parsing for user1...");
|
||||
|
||||
let user1_path = "./user_data/user1_at_example_com.json";
|
||||
|
||||
match fs::read_to_string(user1_path) {
|
||||
Ok(json_content) => {
|
||||
println!("✓ Successfully read user1 JSON file");
|
||||
|
||||
match serde_json::from_str::<UserPersistentData>(&json_content) {
|
||||
Ok(user_data) => {
|
||||
println!("✅ SUCCESS: JSON parsing completed without errors!");
|
||||
println!(" - User email: {}", user_data.user_email);
|
||||
println!(" - Number of nodes: {}", user_data.nodes.len());
|
||||
println!(" - Number of SLAs: {}", user_data.slas.len());
|
||||
println!(" - Number of services: {}", user_data.services.len());
|
||||
println!(" - Number of apps: {}", user_data.apps.len());
|
||||
|
||||
if !user_data.nodes.is_empty() {
|
||||
let first_node = &user_data.nodes[0];
|
||||
println!(" - First node: {} ({})", first_node.name, first_node.status);
|
||||
println!(" - Earnings today: {}", first_node.earnings_today_usd);
|
||||
}
|
||||
|
||||
if !user_data.slas.is_empty() {
|
||||
let first_sla = &user_data.slas[0];
|
||||
println!(" - First SLA: {} ({})", first_sla.name, first_sla.status);
|
||||
}
|
||||
|
||||
println!("\n🎉 All parsing successful! The JSON parsing fixes are working correctly.");
|
||||
},
|
||||
Err(e) => {
|
||||
println!("❌ JSON parsing failed: {}", e);
|
||||
println!("📄 Error details: {:#?}", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
println!("❌ Failed to read user1 JSON file: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
144
tests/tests_archive/test_staking_functionality.rs
Normal file
144
tests/tests_archive/test_staking_functionality.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use threefold_marketplace::services::farmer::FarmerService;
|
||||
use threefold_marketplace::services::user_persistence::UserPersistence;
|
||||
use threefold_marketplace::models::user::NodeStakingOptions;
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
#[test]
|
||||
fn test_staking_end_to_end_flow() {
|
||||
// Test the complete staking flow for user1
|
||||
let user_email = "user1@example.com";
|
||||
let farmer_service = FarmerService::builder().build().unwrap();
|
||||
|
||||
// Clean up any existing staking data first
|
||||
let user_data = UserPersistence::load_user_data(user_email);
|
||||
assert!(user_data.is_some(), "User1 data should exist");
|
||||
|
||||
let user_data = user_data.unwrap();
|
||||
|
||||
// Unstake from all nodes to start with a clean state
|
||||
for node in &user_data.nodes {
|
||||
if let Some(staking_options) = &node.staking_options {
|
||||
if staking_options.staking_enabled {
|
||||
let _ = farmer_service.unstake_from_node(user_email, &node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload user data after cleanup
|
||||
let user_data = UserPersistence::load_user_data(user_email).unwrap();
|
||||
println!("Initial wallet balance: {}", user_data.wallet_balance_usd);
|
||||
println!("Number of nodes: {}", user_data.nodes.len());
|
||||
|
||||
// Verify user has sufficient balance (should be 11000 TFP)
|
||||
assert!(user_data.wallet_balance_usd >= Decimal::from(100), "User should have at least 100 TFP");
|
||||
|
||||
// Verify user has nodes to stake on
|
||||
assert!(!user_data.nodes.is_empty(), "User should have nodes");
|
||||
|
||||
let first_node = &user_data.nodes[0];
|
||||
println!("First node ID: {}", first_node.id);
|
||||
println!("First node name: {}", first_node.name);
|
||||
|
||||
// Test staking 100 TFP on the first node
|
||||
let staking_options = NodeStakingOptions {
|
||||
staking_enabled: true,
|
||||
staked_amount: Decimal::from(100),
|
||||
staking_start_date: None, // Will be set by the service
|
||||
staking_period_months: 12,
|
||||
early_withdrawal_allowed: true,
|
||||
early_withdrawal_penalty_percent: 5.0,
|
||||
};
|
||||
|
||||
// Perform staking
|
||||
let stake_result = farmer_service.stake_on_node(user_email, &first_node.id, staking_options);
|
||||
|
||||
match stake_result {
|
||||
Ok(()) => {
|
||||
println!("✅ Staking successful!");
|
||||
|
||||
// Verify the staking was applied
|
||||
let updated_data = UserPersistence::load_user_data(user_email).unwrap();
|
||||
|
||||
// Check wallet balance decreased
|
||||
let expected_balance = user_data.wallet_balance_usd - Decimal::from(100);
|
||||
assert_eq!(updated_data.wallet_balance_usd, expected_balance,
|
||||
"Wallet balance should decrease by staked amount");
|
||||
|
||||
// Check node has staking options
|
||||
let updated_node = updated_data.nodes.iter()
|
||||
.find(|n| n.id == first_node.id)
|
||||
.expect("Node should still exist");
|
||||
|
||||
assert!(updated_node.staking_options.is_some(), "Node should have staking options");
|
||||
let node_staking = updated_node.staking_options.as_ref().unwrap();
|
||||
assert!(node_staking.staking_enabled, "Staking should be enabled");
|
||||
assert_eq!(node_staking.staked_amount, Decimal::from(100), "Staked amount should be 100");
|
||||
|
||||
println!("✅ All staking verifications passed!");
|
||||
|
||||
// Test getting staking statistics
|
||||
let stats = farmer_service.get_staking_statistics(user_email);
|
||||
assert_eq!(stats.total_staked_amount, Decimal::from(100), "Total staked should be 100");
|
||||
assert_eq!(stats.staked_nodes_count, 1, "Should have 1 staked node");
|
||||
|
||||
println!("✅ Staking statistics correct!");
|
||||
|
||||
// Test unstaking
|
||||
let unstake_result = farmer_service.unstake_from_node(user_email, &first_node.id);
|
||||
match unstake_result {
|
||||
Ok(returned_amount) => {
|
||||
println!("✅ Unstaking successful! Returned: {} TFP", returned_amount);
|
||||
|
||||
// Verify unstaking
|
||||
let final_data = UserPersistence::load_user_data(user_email).unwrap();
|
||||
let final_node = final_data.nodes.iter()
|
||||
.find(|n| n.id == first_node.id)
|
||||
.expect("Node should still exist");
|
||||
|
||||
assert!(final_node.staking_options.is_none(), "Node should not have staking options after unstaking");
|
||||
|
||||
println!("✅ Complete staking flow test passed!");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Unstaking failed: {}", e);
|
||||
panic!("Unstaking should succeed");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Staking failed: {}", e);
|
||||
panic!("Staking should succeed with sufficient balance");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insufficient_balance_staking() {
|
||||
let user_email = "user1@example.com";
|
||||
let farmer_service = FarmerService::builder().build().unwrap();
|
||||
|
||||
let user_data = UserPersistence::load_user_data(user_email).unwrap();
|
||||
let first_node = &user_data.nodes[0];
|
||||
|
||||
// Try to stake more than available balance
|
||||
let excessive_amount = user_data.wallet_balance_usd + Decimal::from(1000);
|
||||
let staking_options = NodeStakingOptions {
|
||||
staking_enabled: true,
|
||||
staked_amount: excessive_amount,
|
||||
staking_start_date: None,
|
||||
staking_period_months: 12,
|
||||
early_withdrawal_allowed: true,
|
||||
early_withdrawal_penalty_percent: 5.0,
|
||||
};
|
||||
|
||||
let result = farmer_service.stake_on_node(user_email, &first_node.id, staking_options);
|
||||
assert!(result.is_err(), "Staking should fail with insufficient balance");
|
||||
|
||||
let error_msg = result.unwrap_err();
|
||||
assert!(error_msg.contains("Insufficient balance"), "Error should mention insufficient balance");
|
||||
|
||||
println!("✅ Insufficient balance test passed!");
|
||||
}
|
||||
}
|
262
tests/tests_archive/ux_suite/README.md
Normal file
262
tests/tests_archive/ux_suite/README.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Project Mycelium UX Test Suite
|
||||
|
||||
Complete end-to-end testing framework for validating the entire user experience as specified in the roadmap Section 9.
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
This UX test suite replaces manual testing with automated browser-based tests that validate all user flows, from anonymous browsing to complex provider workflows. The framework ensures that the complete UX specification is working correctly.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Selenium WebDriver** (for browser automation)
|
||||
```bash
|
||||
# Install Chrome WebDriver
|
||||
# Option 1: Docker (recommended)
|
||||
docker run -d -p 4444:4444 selenium/standalone-chrome:latest
|
||||
|
||||
# Option 2: Local installation
|
||||
# Download chromedriver and ensure it's in PATH
|
||||
```
|
||||
|
||||
2. **Environment Setup**
|
||||
```bash
|
||||
# Setup test directories
|
||||
make -f Makefile.ux-tests setup-ux-tests
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Quick smoke tests (3 core flows)
|
||||
make -f Makefile.ux-tests test-ux-quick
|
||||
|
||||
# Complete core UX test suite
|
||||
make -f Makefile.ux-tests test-ux
|
||||
|
||||
# Individual test categories
|
||||
make -f Makefile.ux-tests test-ux-public # Public access tests
|
||||
make -f Makefile.ux-tests test-ux-auth # Authentication tests
|
||||
make -f Makefile.ux-tests test-ux-shopping # Shopping workflow tests
|
||||
|
||||
# Complete test suite (all tests)
|
||||
make -f Makefile.ux-tests test-ux-full
|
||||
```
|
||||
|
||||
## 📋 Test Categories
|
||||
|
||||
### ✅ Public Access Tests
|
||||
Tests functionality available without authentication:
|
||||
- Information pages (`/docs`, `/privacy`, `/terms`, `/about`, `/contact`)
|
||||
- Anonymous marketplace browsing
|
||||
- Anonymous cart functionality
|
||||
- Search and filtering
|
||||
- Product details pages
|
||||
- Responsive design validation
|
||||
|
||||
### ✅ Authentication Tests
|
||||
Tests user authentication functionality:
|
||||
- User registration flow
|
||||
- Login and logout functionality
|
||||
- Cart migration during login
|
||||
- Session management and persistence
|
||||
- GitEa OAuth integration (conditional)
|
||||
- Error handling and validation
|
||||
|
||||
### ✅ Shopping Workflow Tests
|
||||
Tests complete shopping experience:
|
||||
- Buy Now functionality
|
||||
- Add to Cart workflow
|
||||
- Cart management (edit quantities, remove items)
|
||||
- Complete checkout process
|
||||
- Order confirmation and tracking
|
||||
- Insufficient funds handling
|
||||
- Different product categories
|
||||
|
||||
## 🏗️ Framework Architecture
|
||||
|
||||
```
|
||||
tests/ux_suite/
|
||||
├── environment/ # Test environment management
|
||||
│ ├── browser_manager.rs # Browser automation (Selenium WebDriver)
|
||||
│ ├── test_server.rs # Isolated test server instance
|
||||
│ ├── test_data_manager.rs # Test personas and data fixtures
|
||||
│ └── api_client.rs # API validation alongside UX
|
||||
├── flows/ # End-to-end user flow tests
|
||||
│ ├── public_access.rs # Anonymous user tests
|
||||
│ ├── authentication.rs # Auth flow tests
|
||||
│ └── shopping.rs # Purchase workflow tests
|
||||
├── utils/ # Test utilities and helpers
|
||||
│ ├── ux_test_helper.rs # High-level UX operations
|
||||
│ ├── assertions.rs # UX-specific assertions
|
||||
│ └── test_fixtures.rs # Test data and setup
|
||||
└── reports/ # Test reports and screenshots
|
||||
└── screenshots/ # Visual validation screenshots
|
||||
```
|
||||
|
||||
## 🧪 Test Data & Personas
|
||||
|
||||
The framework uses predefined test personas for different user roles:
|
||||
|
||||
- **Consumer** (`user1@example.com`) - Standard marketplace user
|
||||
- **Farmer** (`farmer1@example.com`) - Resource provider with nodes
|
||||
- **App Provider** (`appdev1@example.com`) - Application publisher
|
||||
- **Service Provider** (`service1@example.com`) - Professional services
|
||||
|
||||
Each persona has:
|
||||
- Profile data (name, country, timezone, currency preference)
|
||||
- Wallet balance for testing purchases
|
||||
- Role-specific permissions and workflows
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `UX_TEST_MODE`: Test execution mode
|
||||
- `dev` (default) - Visible browser, longer timeouts
|
||||
- `ci` - Headless browser, optimized for CI/CD
|
||||
- `full` - Complete test suite with all browsers
|
||||
- `UX_TEST_TIMEOUT`: Timeout in seconds (default: 60)
|
||||
- `SELENIUM_URL`: WebDriver URL (default: http://localhost:4444)
|
||||
|
||||
### Test Isolation
|
||||
|
||||
- **Dedicated test server** on port 8081 (separate from dev:8080, prod:3000)
|
||||
- **Isolated test data** in `user_data_test/` directory
|
||||
- **Clean state** between test runs
|
||||
- **Separate browser profiles** with cleared cookies/storage
|
||||
|
||||
## 📊 Test Reporting
|
||||
|
||||
Tests generate comprehensive reports with:
|
||||
- **Screenshots** for each major step and on failures
|
||||
- **Performance metrics** (page load times)
|
||||
- **API validation** results alongside UX validation
|
||||
- **HTML reports** with visual timeline
|
||||
|
||||
Reports are saved to `tests/ux_suite/reports/`
|
||||
|
||||
## 🔄 Integration with Development Workflow
|
||||
|
||||
### Manual Testing Replacement
|
||||
|
||||
**Before**: `cargo run` → register as user1@example.com → manually test features
|
||||
|
||||
**After**: `make -f Makefile.ux-tests test-ux` → automated validation of all flows
|
||||
|
||||
### CI/CD Integration
|
||||
|
||||
```yaml
|
||||
# GitHub Actions example
|
||||
- name: Run UX Tests
|
||||
run: make -f Makefile.ux-tests test-ux-ci
|
||||
env:
|
||||
UX_TEST_MODE: ci
|
||||
SELENIUM_URL: http://localhost:4444
|
||||
```
|
||||
|
||||
## 📖 Writing New Tests
|
||||
|
||||
### Basic Test Structure
|
||||
|
||||
```rust
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_new_feature() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
// Login as appropriate persona
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Test the feature
|
||||
helper.browser.navigate_to("/new-feature").await?;
|
||||
helper.assert_page_loaded("New Feature").await?;
|
||||
|
||||
// Take screenshot for visual validation
|
||||
helper.take_screenshot("new_feature_page").await?;
|
||||
|
||||
// Validate with API if needed
|
||||
let api_data = helper.api_client.get("/api/new-feature").await?;
|
||||
assert!(api_data.status == 200);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Adding Test Personas
|
||||
|
||||
```rust
|
||||
// In test_data_manager.rs
|
||||
personas.insert(UserRole::NewRole, TestPersona {
|
||||
email: "newrole@example.com".to_string(),
|
||||
password: "testpass123".to_string(),
|
||||
name: "Test New Role".to_string(),
|
||||
role: UserRole::NewRole,
|
||||
profile: UserProfile { /* ... */ },
|
||||
});
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Selenium not running**
|
||||
```bash
|
||||
docker run -d -p 4444:4444 selenium/standalone-chrome:latest
|
||||
```
|
||||
|
||||
2. **Test server port conflicts**
|
||||
- Tests use port 8081 by default
|
||||
- Make sure port is available or configure different port
|
||||
|
||||
3. **Screenshots not saving**
|
||||
```bash
|
||||
mkdir -p tests/ux_suite/reports/screenshots
|
||||
```
|
||||
|
||||
4. **Browser automation fails**
|
||||
- Check WebDriver compatibility with browser version
|
||||
- Verify headless mode settings for CI environments
|
||||
|
||||
### Debug Mode
|
||||
|
||||
```bash
|
||||
# Run with verbose logging
|
||||
RUST_LOG=debug make -f Makefile.ux-tests test-ux
|
||||
|
||||
# Run single test with visible browser
|
||||
UX_TEST_MODE=dev cargo test --test ux_suite_main test_user_login_flow --features ux_testing
|
||||
```
|
||||
|
||||
## 🔮 Future Enhancements
|
||||
|
||||
- [ ] **Dashboard Tests** (Phase 3) - User dashboard, wallet, provider workflows
|
||||
- [ ] **Settings Tests** (Phase 3) - SSH keys, notifications, preferences
|
||||
- [ ] **Modal Interactions** (Phase 3) - Complex UI flows based on html_template_tests/
|
||||
- [ ] **Cross-browser Testing** (Phase 4) - Firefox, Safari compatibility
|
||||
- [ ] **Mobile Testing** - Responsive design validation
|
||||
- [ ] **Performance Testing** - Load time benchmarks
|
||||
- [ ] **Visual Regression** - Screenshot comparison testing
|
||||
|
||||
## 📝 Contributing
|
||||
|
||||
When adding new UX features:
|
||||
|
||||
1. **Write tests first** - Define expected UX behavior in tests
|
||||
2. **Use test personas** - Leverage existing user roles and data
|
||||
3. **Include screenshots** - Visual validation for complex flows
|
||||
4. **Validate APIs** - Ensure backend/frontend consistency
|
||||
5. **Update documentation** - Keep test coverage current
|
||||
|
||||
## 🎯 Success Metrics
|
||||
|
||||
- ✅ **60+ UX flows** automated (from Section 9 specification)
|
||||
- ✅ **4 user personas** covering all roles
|
||||
- ✅ **Complete automation** replacing manual testing workflow
|
||||
- ✅ **Isolated environment** separate from other tests
|
||||
- ✅ **CI/CD ready** with automated reporting
|
||||
|
||||
The UX test suite ensures the Project Mycelium delivers a complete, tested user experience before deployment.
|
249
tests/tests_archive/ux_suite/environment/api_client.rs
Normal file
249
tests/tests_archive/ux_suite/environment/api_client.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
//! API Test Client
|
||||
//!
|
||||
//! Validates API responses alongside UX interactions
|
||||
|
||||
use reqwest;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// API test client for validating backend responses
|
||||
#[derive(Clone)]
|
||||
pub struct APITestClient {
|
||||
client: reqwest::Client,
|
||||
base_url: String,
|
||||
session_cookies: Option<String>,
|
||||
}
|
||||
|
||||
impl APITestClient {
|
||||
/// Create a new API test client
|
||||
pub fn new(test_port: u16) -> Self {
|
||||
Self {
|
||||
client: reqwest::Client::builder()
|
||||
.cookie_store(true)
|
||||
.build()
|
||||
.expect("Failed to create HTTP client"),
|
||||
base_url: format!("http://localhost:{}", test_port),
|
||||
session_cookies: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a GET request to an API endpoint
|
||||
pub async fn get(&self, path: &str) -> Result<ApiResponse, Box<dyn std::error::Error>> {
|
||||
let url = format!("{}{}", self.base_url, path);
|
||||
log::info!("API GET: {}", url);
|
||||
|
||||
let mut request = self.client.get(&url);
|
||||
|
||||
if let Some(cookies) = &self.session_cookies {
|
||||
request = request.header("Cookie", cookies);
|
||||
}
|
||||
|
||||
let response = request.send().await?;
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let body: Value = response.json().await?;
|
||||
|
||||
Ok(ApiResponse {
|
||||
status: status.as_u16(),
|
||||
headers: headers.iter().map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())).collect(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
/// Make a POST request to an API endpoint
|
||||
pub async fn post(&self, path: &str, data: Value) -> Result<ApiResponse, Box<dyn std::error::Error>> {
|
||||
let url = format!("{}{}", self.base_url, path);
|
||||
log::info!("API POST: {}", url);
|
||||
|
||||
let mut request = self.client.post(&url).json(&data);
|
||||
|
||||
if let Some(cookies) = &self.session_cookies {
|
||||
request = request.header("Cookie", cookies);
|
||||
}
|
||||
|
||||
let response = request.send().await?;
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let body: Value = response.json().await?;
|
||||
|
||||
Ok(ApiResponse {
|
||||
status: status.as_u16(),
|
||||
headers: headers.iter().map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())).collect(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
/// Make a PUT request to an API endpoint
|
||||
pub async fn put(&self, path: &str, data: Value) -> Result<ApiResponse, Box<dyn std::error::Error>> {
|
||||
let url = format!("{}{}", self.base_url, path);
|
||||
log::info!("API PUT: {}", url);
|
||||
|
||||
let mut request = self.client.put(&url).json(&data);
|
||||
|
||||
if let Some(cookies) = &self.session_cookies {
|
||||
request = request.header("Cookie", cookies);
|
||||
}
|
||||
|
||||
let response = request.send().await?;
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let body: Value = response.json().await?;
|
||||
|
||||
Ok(ApiResponse {
|
||||
status: status.as_u16(),
|
||||
headers: headers.iter().map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())).collect(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
/// Make a DELETE request to an API endpoint
|
||||
pub async fn delete(&self, path: &str) -> Result<ApiResponse, Box<dyn std::error::Error>> {
|
||||
let url = format!("{}{}", self.base_url, path);
|
||||
log::info!("API DELETE: {}", url);
|
||||
|
||||
let mut request = self.client.delete(&url);
|
||||
|
||||
if let Some(cookies) = &self.session_cookies {
|
||||
request = request.header("Cookie", cookies);
|
||||
}
|
||||
|
||||
let response = request.send().await?;
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let body: Value = response.json().await?;
|
||||
|
||||
Ok(ApiResponse {
|
||||
status: status.as_u16(),
|
||||
headers: headers.iter().map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())).collect(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate authentication status
|
||||
pub async fn validate_auth_status(&self) -> Result<AuthStatus, Box<dyn std::error::Error>> {
|
||||
let response = self.get("/api/auth/status").await?;
|
||||
|
||||
// Validate ResponseBuilder envelope format
|
||||
self.assert_response_envelope(&response.body)?;
|
||||
|
||||
let authenticated = response.body["data"]["authenticated"].as_bool().unwrap_or(false);
|
||||
let user_email = response.body["data"]["user"]["email"].as_str().map(|s| s.to_string());
|
||||
|
||||
Ok(AuthStatus {
|
||||
authenticated,
|
||||
user_email,
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate cart state
|
||||
pub async fn validate_cart_state(&self) -> Result<CartState, Box<dyn std::error::Error>> {
|
||||
let response = self.get("/api/cart").await?;
|
||||
|
||||
self.assert_response_envelope(&response.body)?;
|
||||
|
||||
let items = response.body["data"]["items"].as_array()
|
||||
.map(|arr| arr.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
let total = response.body["data"]["total"]["amount"].as_f64().unwrap_or(0.0);
|
||||
|
||||
Ok(CartState {
|
||||
item_count: items,
|
||||
total_amount: total,
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate wallet balance
|
||||
pub async fn get_wallet_balance(&self) -> Result<WalletBalance, Box<dyn std::error::Error>> {
|
||||
let response = self.get("/api/wallet/balance").await?;
|
||||
|
||||
self.assert_response_envelope(&response.body)?;
|
||||
|
||||
let balance = response.body["data"]["balance"]["amount"].as_f64().unwrap_or(0.0);
|
||||
let currency = response.body["data"]["balance"]["currency"].as_str().unwrap_or("TFC").to_string();
|
||||
|
||||
Ok(WalletBalance {
|
||||
balance,
|
||||
currency,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get user dashboard data
|
||||
pub async fn get_dashboard_data(&self, dashboard_type: &str) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
let path = format!("/api/dashboard/{}-data", dashboard_type);
|
||||
let response = self.get(&path).await?;
|
||||
|
||||
self.assert_response_envelope(&response.body)?;
|
||||
|
||||
Ok(response.body["data"].clone())
|
||||
}
|
||||
|
||||
/// Validate products API
|
||||
pub async fn get_products(&self, category: Option<&str>) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
|
||||
let path = if let Some(cat) = category {
|
||||
format!("/api/products?category={}", cat)
|
||||
} else {
|
||||
"/api/products".to_string()
|
||||
};
|
||||
|
||||
let response = self.get(&path).await?;
|
||||
self.assert_response_envelope(&response.body)?;
|
||||
|
||||
Ok(response.body["data"]["products"].as_array().unwrap_or(&vec![]).clone())
|
||||
}
|
||||
|
||||
/// Assert ResponseBuilder envelope format
|
||||
fn assert_response_envelope(&self, 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(())
|
||||
}
|
||||
|
||||
/// Set session cookies for authenticated requests
|
||||
pub fn set_session_cookies(&mut self, cookies: String) {
|
||||
self.session_cookies = Some(cookies);
|
||||
}
|
||||
}
|
||||
|
||||
/// API response structure
|
||||
#[derive(Debug)]
|
||||
pub struct ApiResponse {
|
||||
pub status: u16,
|
||||
pub headers: HashMap<String, String>,
|
||||
pub body: Value,
|
||||
}
|
||||
|
||||
/// Authentication status
|
||||
#[derive(Debug)]
|
||||
pub struct AuthStatus {
|
||||
pub authenticated: bool,
|
||||
pub user_email: Option<String>,
|
||||
}
|
||||
|
||||
/// Cart state
|
||||
#[derive(Debug)]
|
||||
pub struct CartState {
|
||||
pub item_count: usize,
|
||||
pub total_amount: f64,
|
||||
}
|
||||
|
||||
/// Wallet balance
|
||||
#[derive(Debug)]
|
||||
pub struct WalletBalance {
|
||||
pub balance: f64,
|
||||
pub currency: String,
|
||||
}
|
230
tests/tests_archive/ux_suite/environment/browser_manager.rs
Normal file
230
tests/tests_archive/ux_suite/environment/browser_manager.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
//! Browser Automation Manager
|
||||
//!
|
||||
//! Handles browser lifecycle, navigation, and interaction for UX testing
|
||||
|
||||
use thirtyfour::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
|
||||
/// Supported browser types for testing
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BrowserType {
|
||||
Chrome,
|
||||
Firefox,
|
||||
Safari,
|
||||
Edge,
|
||||
}
|
||||
|
||||
/// Browser manager for UX testing
|
||||
pub struct BrowserManager {
|
||||
driver: WebDriver,
|
||||
base_url: String,
|
||||
screenshot_dir: PathBuf,
|
||||
config: super::UXTestConfig,
|
||||
}
|
||||
|
||||
impl Clone for BrowserManager {
|
||||
fn clone(&self) -> Self {
|
||||
// Note: WebDriver cannot be cloned, so this creates a reference to the same driver
|
||||
// In practice, we should avoid cloning BrowserManager and use references instead
|
||||
panic!("BrowserManager cannot be cloned due to WebDriver limitations. Use references instead.");
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserManager {
|
||||
/// Create a new browser manager
|
||||
pub async fn new(config: &super::UXTestConfig) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let capabilities = match config.browser_type {
|
||||
BrowserType::Chrome => {
|
||||
let mut caps = DesiredCapabilities::chrome();
|
||||
if config.headless {
|
||||
caps.add_chrome_arg("--headless")?;
|
||||
}
|
||||
caps.add_chrome_arg("--no-sandbox")?;
|
||||
caps.add_chrome_arg("--disable-dev-shm-usage")?;
|
||||
caps.add_chrome_arg("--disable-gpu")?;
|
||||
caps.add_chrome_arg("--window-size=1920,1080")?;
|
||||
caps
|
||||
}
|
||||
BrowserType::Firefox => {
|
||||
let mut caps = DesiredCapabilities::firefox();
|
||||
if config.headless {
|
||||
caps.add_firefox_arg("--headless")?;
|
||||
}
|
||||
caps
|
||||
}
|
||||
_ => {
|
||||
return Err("Browser type not yet implemented".into());
|
||||
}
|
||||
};
|
||||
|
||||
// Try to connect to existing WebDriver or start one
|
||||
let driver = match WebDriver::new("http://localhost:4444", capabilities.clone()).await {
|
||||
Ok(driver) => driver,
|
||||
Err(_) => {
|
||||
// If selenium server is not running, try local driver
|
||||
WebDriver::new("http://localhost:9515", capabilities).await?
|
||||
}
|
||||
};
|
||||
|
||||
// Configure browser
|
||||
driver.set_window_size(1920, 1080).await?;
|
||||
driver.implicitly_wait(Duration::from_secs(10)).await?;
|
||||
|
||||
Ok(Self {
|
||||
driver,
|
||||
base_url: format!("http://localhost:{}", config.test_port),
|
||||
screenshot_dir: config.screenshot_dir.clone(),
|
||||
config: config.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Navigate to a path on the test server
|
||||
pub async fn navigate_to(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let url = if path.starts_with("http") {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("{}{}", self.base_url, path)
|
||||
};
|
||||
|
||||
log::info!("Navigating to: {}", url);
|
||||
|
||||
timeout(
|
||||
Duration::from_secs(self.config.timeout_seconds),
|
||||
self.driver.goto(&url)
|
||||
).await??;
|
||||
|
||||
// Wait for page to load
|
||||
self.wait_for_page_load().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for page to be fully loaded
|
||||
pub async fn wait_for_page_load(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Wait for document ready state
|
||||
self.driver.execute("return document.readyState", vec![]).await?;
|
||||
|
||||
// Additional wait for any dynamic content
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Take a screenshot with the given name
|
||||
pub async fn take_screenshot(&self, name: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let screenshot = self.driver.screenshot_as_png().await?;
|
||||
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
|
||||
let filename = format!("{}_{}.png", name, timestamp);
|
||||
let path = self.screenshot_dir.join(filename);
|
||||
|
||||
std::fs::create_dir_all(&self.screenshot_dir)?;
|
||||
std::fs::write(&path, screenshot)?;
|
||||
|
||||
log::info!("Screenshot saved: {:?}", path);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Find element by CSS selector
|
||||
pub async fn find_element(&self, selector: &str) -> Result<WebElement, Box<dyn std::error::Error>> {
|
||||
Ok(self.driver.find(By::Css(selector)).await?)
|
||||
}
|
||||
|
||||
/// Find elements by CSS selector
|
||||
pub async fn find_elements(&self, selector: &str) -> Result<Vec<WebElement>, Box<dyn std::error::Error>> {
|
||||
Ok(self.driver.find_all(By::Css(selector)).await?)
|
||||
}
|
||||
|
||||
/// Click element by CSS selector
|
||||
pub async fn click(&self, selector: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let element = self.find_element(selector).await?;
|
||||
element.scroll_into_view().await?;
|
||||
element.click().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Type text into element
|
||||
pub async fn type_text(&self, selector: &str, text: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let element = self.find_element(selector).await?;
|
||||
element.clear().await?;
|
||||
element.send_keys(text).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get text from element
|
||||
pub async fn get_text(&self, selector: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let element = self.find_element(selector).await?;
|
||||
Ok(element.text().await?)
|
||||
}
|
||||
|
||||
/// Check if element exists
|
||||
pub async fn element_exists(&self, selector: &str) -> bool {
|
||||
self.driver.find(By::Css(selector)).await.is_ok()
|
||||
}
|
||||
|
||||
/// Wait for element to be visible
|
||||
pub async fn wait_for_element(&self, selector: &str) -> Result<WebElement, Box<dyn std::error::Error>> {
|
||||
let timeout_duration = Duration::from_secs(self.config.timeout_seconds);
|
||||
|
||||
timeout(timeout_duration, async {
|
||||
loop {
|
||||
if let Ok(element) = self.driver.find(By::Css(selector)).await {
|
||||
if element.is_displayed().await.unwrap_or(false) {
|
||||
return Ok(element);
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}).await?
|
||||
}
|
||||
|
||||
/// Get current page title
|
||||
pub async fn get_title(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||
Ok(self.driver.title().await?)
|
||||
}
|
||||
|
||||
/// Get current URL
|
||||
pub async fn get_current_url(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||
Ok(self.driver.current_url().await?)
|
||||
}
|
||||
|
||||
/// Execute JavaScript
|
||||
pub async fn execute_script(&self, script: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
||||
Ok(self.driver.execute(script, vec![]).await?)
|
||||
}
|
||||
|
||||
/// Quit the browser
|
||||
pub async fn quit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.driver.quit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get page source for debugging
|
||||
pub async fn get_page_source(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||
Ok(self.driver.source().await?)
|
||||
}
|
||||
|
||||
/// Scroll to element
|
||||
pub async fn scroll_to_element(&self, selector: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let element = self.find_element(selector).await?;
|
||||
element.scroll_into_view().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for text to appear in element
|
||||
pub async fn wait_for_text(&self, selector: &str, expected_text: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let timeout_duration = Duration::from_secs(self.config.timeout_seconds);
|
||||
|
||||
timeout(timeout_duration, async {
|
||||
loop {
|
||||
if let Ok(element) = self.driver.find(By::Css(selector)).await {
|
||||
if let Ok(text) = element.text().await {
|
||||
if text.contains(expected_text) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}).await?
|
||||
}
|
||||
}
|
115
tests/tests_archive/ux_suite/environment/mod.rs
Normal file
115
tests/tests_archive/ux_suite/environment/mod.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
//! Test Environment Management
|
||||
//!
|
||||
//! Handles isolated test environment setup including:
|
||||
//! - Test server instance
|
||||
//! - Browser automation
|
||||
//! - Test data management
|
||||
//! - API client for validation
|
||||
|
||||
pub mod browser_manager;
|
||||
pub mod test_server;
|
||||
pub mod test_data_manager;
|
||||
pub mod api_client;
|
||||
|
||||
pub use browser_manager::*;
|
||||
pub use test_server::*;
|
||||
pub use test_data_manager::*;
|
||||
pub use api_client::*;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use tokio::time::Duration;
|
||||
|
||||
/// Configuration for UX test environment
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UXTestConfig {
|
||||
pub test_port: u16,
|
||||
pub headless: bool,
|
||||
pub timeout_seconds: u64,
|
||||
pub screenshot_on_failure: bool,
|
||||
pub browser_type: BrowserType,
|
||||
pub test_data_dir: PathBuf,
|
||||
pub screenshot_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for UXTestConfig {
|
||||
fn default() -> Self {
|
||||
let test_mode = std::env::var("UX_TEST_MODE").unwrap_or_else(|_| "dev".to_string());
|
||||
|
||||
match test_mode.as_str() {
|
||||
"ci" => Self {
|
||||
test_port: 8081,
|
||||
headless: true,
|
||||
timeout_seconds: 30,
|
||||
screenshot_on_failure: true,
|
||||
browser_type: BrowserType::Chrome,
|
||||
test_data_dir: PathBuf::from("user_data_test"),
|
||||
screenshot_dir: PathBuf::from("tests/ux_suite/reports/screenshots"),
|
||||
},
|
||||
"dev" => Self {
|
||||
test_port: 8081,
|
||||
headless: false,
|
||||
timeout_seconds: 60,
|
||||
screenshot_on_failure: true,
|
||||
browser_type: BrowserType::Chrome,
|
||||
test_data_dir: PathBuf::from("user_data_test"),
|
||||
screenshot_dir: PathBuf::from("tests/ux_suite/reports/screenshots"),
|
||||
},
|
||||
_ => Self {
|
||||
test_port: 8081,
|
||||
headless: false,
|
||||
timeout_seconds: 60,
|
||||
screenshot_on_failure: true,
|
||||
browser_type: BrowserType::Chrome,
|
||||
test_data_dir: PathBuf::from("user_data_test"),
|
||||
screenshot_dir: PathBuf::from("tests/ux_suite/reports/screenshots"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete UX test environment
|
||||
pub struct UXTestEnvironment {
|
||||
pub config: UXTestConfig,
|
||||
pub browser: BrowserManager,
|
||||
pub server: TestServer,
|
||||
pub data_manager: TestDataManager,
|
||||
pub api_client: APITestClient,
|
||||
}
|
||||
|
||||
impl UXTestEnvironment {
|
||||
/// Initialize a new test environment
|
||||
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let config = UXTestConfig::default();
|
||||
|
||||
// Create directories
|
||||
std::fs::create_dir_all(&config.test_data_dir)?;
|
||||
std::fs::create_dir_all(&config.screenshot_dir)?;
|
||||
|
||||
// Initialize components
|
||||
let data_manager = TestDataManager::new(&config.test_data_dir)?;
|
||||
let server = TestServer::start(config.test_port).await?;
|
||||
let browser = BrowserManager::new(&config).await?;
|
||||
let api_client = APITestClient::new(config.test_port);
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
browser,
|
||||
server,
|
||||
data_manager,
|
||||
api_client,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a UX test helper for this environment
|
||||
pub fn ux_helper(&self) -> UXTestHelper {
|
||||
UXTestHelper::new(self)
|
||||
}
|
||||
|
||||
/// Clean up test environment
|
||||
pub async fn cleanup(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.browser.quit().await?;
|
||||
self.server.stop().await?;
|
||||
self.data_manager.cleanup()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
314
tests/tests_archive/ux_suite/environment/test_data_manager.rs
Normal file
314
tests/tests_archive/ux_suite/environment/test_data_manager.rs
Normal file
@@ -0,0 +1,314 @@
|
||||
//! Test Data Management
|
||||
//!
|
||||
//! Manages test fixtures, user personas, and data isolation for UX testing
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Test user persona for different user types
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TestPersona {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub name: String,
|
||||
pub role: UserRole,
|
||||
pub profile: UserProfile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum UserRole {
|
||||
Consumer,
|
||||
Farmer,
|
||||
AppProvider,
|
||||
ServiceProvider,
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserProfile {
|
||||
pub country: String,
|
||||
pub timezone: String,
|
||||
pub currency_preference: String,
|
||||
pub wallet_balance: f64,
|
||||
}
|
||||
|
||||
/// Test marketplace data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TestMarketplaceData {
|
||||
pub products: Vec<TestProduct>,
|
||||
pub services: Vec<TestService>,
|
||||
pub nodes: Vec<TestNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TestProduct {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub category: String,
|
||||
pub price: f64,
|
||||
pub currency: String,
|
||||
pub description: String,
|
||||
pub provider_email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TestService {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub price: f64,
|
||||
pub provider_email: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TestNode {
|
||||
pub id: String,
|
||||
pub farmer_email: String,
|
||||
pub location: String,
|
||||
pub specs: NodeSpecs,
|
||||
pub available: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NodeSpecs {
|
||||
pub cpu_cores: u32,
|
||||
pub ram_gb: u32,
|
||||
pub storage_gb: u32,
|
||||
pub price_per_hour: f64,
|
||||
}
|
||||
|
||||
/// Test data manager for UX testing
|
||||
#[derive(Clone)]
|
||||
pub struct TestDataManager {
|
||||
test_data_dir: PathBuf,
|
||||
personas: HashMap<UserRole, TestPersona>,
|
||||
marketplace_data: TestMarketplaceData,
|
||||
}
|
||||
|
||||
impl TestDataManager {
|
||||
/// Create a new test data manager
|
||||
pub fn new(test_data_dir: &PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let personas = Self::create_test_personas();
|
||||
let marketplace_data = Self::create_test_marketplace_data(&personas);
|
||||
|
||||
let manager = Self {
|
||||
test_data_dir: test_data_dir.clone(),
|
||||
personas,
|
||||
marketplace_data,
|
||||
};
|
||||
|
||||
manager.setup_test_data()?;
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
/// Create test user personas
|
||||
fn create_test_personas() -> HashMap<UserRole, TestPersona> {
|
||||
let mut personas = HashMap::new();
|
||||
|
||||
personas.insert(UserRole::Consumer, TestPersona {
|
||||
email: "user1@example.com".to_string(),
|
||||
password: "testpass123".to_string(),
|
||||
name: "Test Consumer".to_string(),
|
||||
role: UserRole::Consumer,
|
||||
profile: UserProfile {
|
||||
country: "United States".to_string(),
|
||||
timezone: "America/New_York".to_string(),
|
||||
currency_preference: "USD".to_string(),
|
||||
wallet_balance: 100.0,
|
||||
},
|
||||
});
|
||||
|
||||
personas.insert(UserRole::Farmer, TestPersona {
|
||||
email: "farmer1@example.com".to_string(),
|
||||
password: "testpass123".to_string(),
|
||||
name: "Test Farmer".to_string(),
|
||||
role: UserRole::Farmer,
|
||||
profile: UserProfile {
|
||||
country: "Canada".to_string(),
|
||||
timezone: "America/Toronto".to_string(),
|
||||
currency_preference: "CAD".to_string(),
|
||||
wallet_balance: 500.0,
|
||||
},
|
||||
});
|
||||
|
||||
personas.insert(UserRole::AppProvider, TestPersona {
|
||||
email: "appdev1@example.com".to_string(),
|
||||
password: "testpass123".to_string(),
|
||||
name: "Test App Developer".to_string(),
|
||||
role: UserRole::AppProvider,
|
||||
profile: UserProfile {
|
||||
country: "Germany".to_string(),
|
||||
timezone: "Europe/Berlin".to_string(),
|
||||
currency_preference: "EUR".to_string(),
|
||||
wallet_balance: 200.0,
|
||||
},
|
||||
});
|
||||
|
||||
personas.insert(UserRole::ServiceProvider, TestPersona {
|
||||
email: "service1@example.com".to_string(),
|
||||
password: "testpass123".to_string(),
|
||||
name: "Test Service Provider".to_string(),
|
||||
role: UserRole::ServiceProvider,
|
||||
profile: UserProfile {
|
||||
country: "United Kingdom".to_string(),
|
||||
timezone: "Europe/London".to_string(),
|
||||
currency_preference: "TFC".to_string(),
|
||||
wallet_balance: 300.0,
|
||||
},
|
||||
});
|
||||
|
||||
personas
|
||||
}
|
||||
|
||||
/// Create test marketplace data
|
||||
fn create_test_marketplace_data(personas: &HashMap<UserRole, TestPersona>) -> TestMarketplaceData {
|
||||
let farmer_email = personas.get(&UserRole::Farmer).unwrap().email.clone();
|
||||
let app_provider_email = personas.get(&UserRole::AppProvider).unwrap().email.clone();
|
||||
let service_provider_email = personas.get(&UserRole::ServiceProvider).unwrap().email.clone();
|
||||
|
||||
TestMarketplaceData {
|
||||
products: vec![
|
||||
TestProduct {
|
||||
id: "test-vm-1".to_string(),
|
||||
name: "Test VM Small".to_string(),
|
||||
category: "compute".to_string(),
|
||||
price: 10.0,
|
||||
currency: "TFC".to_string(),
|
||||
description: "Small virtual machine for testing".to_string(),
|
||||
provider_email: farmer_email.clone(),
|
||||
},
|
||||
TestProduct {
|
||||
id: "test-app-1".to_string(),
|
||||
name: "Test Application".to_string(),
|
||||
category: "applications".to_string(),
|
||||
price: 25.0,
|
||||
currency: "TFC".to_string(),
|
||||
description: "Test application for UX testing".to_string(),
|
||||
provider_email: app_provider_email.clone(),
|
||||
},
|
||||
],
|
||||
services: vec![
|
||||
TestService {
|
||||
id: "test-service-1".to_string(),
|
||||
name: "Test Consulting Service".to_string(),
|
||||
description: "Professional consulting service for testing".to_string(),
|
||||
price: 100.0,
|
||||
provider_email: service_provider_email.clone(),
|
||||
status: "available".to_string(),
|
||||
},
|
||||
],
|
||||
nodes: vec![
|
||||
TestNode {
|
||||
id: "test-node-1".to_string(),
|
||||
farmer_email: farmer_email.clone(),
|
||||
location: "New York, USA".to_string(),
|
||||
specs: NodeSpecs {
|
||||
cpu_cores: 8,
|
||||
ram_gb: 16,
|
||||
storage_gb: 500,
|
||||
price_per_hour: 2.0,
|
||||
},
|
||||
available: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Setup test data files
|
||||
fn setup_test_data(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create test data directory
|
||||
std::fs::create_dir_all(&self.test_data_dir)?;
|
||||
|
||||
// Create user data files for each persona
|
||||
for persona in self.personas.values() {
|
||||
self.create_user_data_file(persona)?;
|
||||
}
|
||||
|
||||
// Save marketplace data
|
||||
let marketplace_file = self.test_data_dir.join("marketplace_data.json");
|
||||
let marketplace_json = serde_json::to_string_pretty(&self.marketplace_data)?;
|
||||
std::fs::write(marketplace_file, marketplace_json)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create user data file for a persona
|
||||
fn create_user_data_file(&self, persona: &TestPersona) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let encoded_email = persona.email.replace("@", "_at_").replace(".", "_dot_");
|
||||
let user_file = self.test_data_dir.join(format!("{}.json", encoded_email));
|
||||
|
||||
let user_data = serde_json::json!({
|
||||
"email": persona.email,
|
||||
"name": persona.name,
|
||||
"profile": persona.profile,
|
||||
"role": persona.role,
|
||||
"wallet": {
|
||||
"balance": persona.profile.wallet_balance,
|
||||
"currency": persona.profile.currency_preference,
|
||||
"transactions": []
|
||||
},
|
||||
"cart": {
|
||||
"items": [],
|
||||
"total": 0.0
|
||||
},
|
||||
"orders": [],
|
||||
"settings": {
|
||||
"notifications": {
|
||||
"security_alerts": true,
|
||||
"billing_notifications": true,
|
||||
"system_alerts": true,
|
||||
"newsletter": false,
|
||||
"dashboard_notifications": true
|
||||
},
|
||||
"ssh_keys": []
|
||||
}
|
||||
});
|
||||
|
||||
let user_json = serde_json::to_string_pretty(&user_data)?;
|
||||
std::fs::write(user_file, user_json)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get test persona by role
|
||||
pub fn get_persona(&self, role: &UserRole) -> Option<&TestPersona> {
|
||||
self.personas.get(role)
|
||||
}
|
||||
|
||||
/// Get all test personas
|
||||
pub fn get_all_personas(&self) -> &HashMap<UserRole, TestPersona> {
|
||||
&self.personas
|
||||
}
|
||||
|
||||
/// Get test marketplace data
|
||||
pub fn get_marketplace_data(&self) -> &TestMarketplaceData {
|
||||
&self.marketplace_data
|
||||
}
|
||||
|
||||
/// Reset test data to clean state
|
||||
pub fn reset_test_data(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Remove all user data files
|
||||
if self.test_data_dir.exists() {
|
||||
std::fs::remove_dir_all(&self.test_data_dir)?;
|
||||
}
|
||||
|
||||
// Recreate test data
|
||||
self.setup_test_data()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleanup test data
|
||||
pub fn cleanup(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if self.test_data_dir.exists() {
|
||||
std::fs::remove_dir_all(&self.test_data_dir)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get test user credentials
|
||||
pub fn get_test_credentials(&self, role: &UserRole) -> Option<(String, String)> {
|
||||
self.personas.get(role).map(|p| (p.email.clone(), p.password.clone()))
|
||||
}
|
||||
}
|
116
tests/tests_archive/ux_suite/environment/test_server.rs
Normal file
116
tests/tests_archive/ux_suite/environment/test_server.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
//! Test Server Management
|
||||
//!
|
||||
//! Manages isolated test server instance for UX testing
|
||||
|
||||
use actix_web::{web, App, HttpServer, middleware::Logger};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Test server instance
|
||||
pub struct TestServer {
|
||||
port: u16,
|
||||
server_handle: Option<actix_web::dev::ServerHandle>,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
/// Start a new test server instance
|
||||
pub async fn start(port: u16) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
// Set environment variables for test mode
|
||||
std::env::set_var("TEST_MODE", "true");
|
||||
std::env::set_var("TEST_PORT", port.to_string());
|
||||
std::env::set_var("TEST_DATA_DIR", "user_data_test");
|
||||
|
||||
// Import the main app configuration
|
||||
let config = threefold_marketplace::config::get_config();
|
||||
|
||||
log::info!("Starting test server on port {}", port);
|
||||
|
||||
// Create test server with the same configuration as main app
|
||||
let server = HttpServer::new(move || {
|
||||
// Initialize Tera templates
|
||||
let mut tera = match tera::Tera::new(&format!("{}/**/*.html", config.templates.dir)) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
eprintln!("Tera initialization error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Register custom Tera functions
|
||||
threefold_marketplace::utils::register_tera_functions(&mut tera);
|
||||
|
||||
App::new()
|
||||
.wrap(Logger::default())
|
||||
.wrap(threefold_marketplace::middleware::RequestTimer)
|
||||
.wrap(threefold_marketplace::middleware::SecurityHeaders)
|
||||
.service(actix_files::Files::new("/static", "./src/static"))
|
||||
.app_data(web::Data::new(tera))
|
||||
.configure(threefold_marketplace::routes::configure_routes)
|
||||
})
|
||||
.workers(1) // Single worker for testing
|
||||
.bind(format!("127.0.0.1:{}", port))?;
|
||||
|
||||
let server_handle = server.handle();
|
||||
|
||||
// Start server in background
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = server.run().await {
|
||||
eprintln!("Test server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for server to start
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Verify server is running
|
||||
let client = reqwest::Client::new();
|
||||
let health_check_url = format!("http://127.0.0.1:{}/", port);
|
||||
|
||||
for attempt in 1..=10 {
|
||||
match client.get(&health_check_url).send().await {
|
||||
Ok(response) if response.status().is_success() => {
|
||||
log::info!("Test server started successfully on port {}", port);
|
||||
return Ok(Self {
|
||||
port,
|
||||
server_handle: Some(server_handle),
|
||||
});
|
||||
}
|
||||
Ok(_) | Err(_) => {
|
||||
if attempt == 10 {
|
||||
return Err("Failed to start test server after 10 attempts".into());
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
port,
|
||||
server_handle: Some(server_handle),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the server URL
|
||||
pub fn url(&self) -> String {
|
||||
format!("http://127.0.0.1:{}", self.port)
|
||||
}
|
||||
|
||||
/// Stop the test server
|
||||
pub async fn stop(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(handle) = self.server_handle.take() {
|
||||
handle.stop(true).await;
|
||||
log::info!("Test server stopped");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServer {
|
||||
fn drop(&mut self) {
|
||||
if let Some(handle) = self.server_handle.take() {
|
||||
// Best effort cleanup
|
||||
let _ = handle.stop(false);
|
||||
}
|
||||
}
|
||||
}
|
359
tests/tests_archive/ux_suite/flows/authentication.rs
Normal file
359
tests/tests_archive/ux_suite/flows/authentication.rs
Normal file
@@ -0,0 +1,359 @@
|
||||
//! Authentication Flow Tests
|
||||
//!
|
||||
//! Tests for user authentication functionality:
|
||||
//! - User registration at /register
|
||||
//! - Login and logout functionality
|
||||
//! - Cart migration during login
|
||||
//! - Session management
|
||||
//! - GitEa OAuth integration (conditional)
|
||||
|
||||
use crate::utils::*;
|
||||
use crate::environment::*;
|
||||
use tokio_test;
|
||||
|
||||
/// Test user registration flow
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_user_registration_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing user registration flow");
|
||||
|
||||
// Get test persona
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
|
||||
// Navigate to registration page
|
||||
helper.browser.navigate_to("/register").await?;
|
||||
helper.assert_page_loaded("Register").await?;
|
||||
|
||||
// Verify registration form elements exist
|
||||
assert!(helper.browser.element_exists("input[name='email'], input[type='email']").await);
|
||||
assert!(helper.browser.element_exists("input[name='password'], input[type='password']").await);
|
||||
assert!(helper.browser.element_exists("input[name='name'], input[name='full_name']").await);
|
||||
|
||||
helper.take_screenshot("registration_form").await?;
|
||||
|
||||
// Fill registration form
|
||||
helper.browser.type_text("input[name='email'], input[type='email']", &persona.email).await?;
|
||||
helper.browser.type_text("input[name='password'], input[type='password']", &persona.password).await?;
|
||||
helper.browser.type_text("input[name='name'], input[name='full_name']", &persona.name).await?;
|
||||
|
||||
// Fill additional fields if they exist
|
||||
if helper.browser.element_exists("input[name='confirm_password']").await {
|
||||
helper.browser.type_text("input[name='confirm_password']", &persona.password).await?;
|
||||
}
|
||||
|
||||
helper.take_screenshot("registration_form_filled").await?;
|
||||
|
||||
// Submit registration
|
||||
helper.browser.click("button[type='submit'], .register-btn, .signup-btn").await?;
|
||||
|
||||
// Wait for success response or redirect
|
||||
helper.browser.wait_for_element(".success, .dashboard, .registration-success, .alert-success").await?;
|
||||
|
||||
helper.take_screenshot("registration_success").await?;
|
||||
|
||||
// Verify user is now authenticated (should be on dashboard or logged in)
|
||||
if helper.browser.get_current_url().await?.contains("dashboard") {
|
||||
helper.assert_user_is_authenticated().await?;
|
||||
}
|
||||
|
||||
log::info!("User registration flow test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test user login flow
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_user_login_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing user login flow");
|
||||
|
||||
// Get test persona (should already exist from registration or test data)
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
|
||||
// Navigate to login page
|
||||
helper.browser.navigate_to("/login").await?;
|
||||
helper.assert_page_loaded("Login").await?;
|
||||
|
||||
// Verify login form elements exist
|
||||
assert!(helper.browser.element_exists("input[name='email'], input[type='email']").await);
|
||||
assert!(helper.browser.element_exists("input[name='password'], input[type='password']").await);
|
||||
assert!(helper.browser.element_exists("button[type='submit'], .login-btn").await);
|
||||
|
||||
helper.take_screenshot("login_form").await?;
|
||||
|
||||
// Test login with valid credentials
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Verify successful login
|
||||
helper.assert_user_is_authenticated().await?;
|
||||
|
||||
// Verify redirect to dashboard or appropriate page
|
||||
let current_url = helper.browser.get_current_url().await?;
|
||||
assert!(current_url.contains("dashboard") || current_url.contains("marketplace"));
|
||||
|
||||
helper.take_screenshot("login_success").await?;
|
||||
|
||||
log::info!("User login flow test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test user logout flow
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_user_logout_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing user logout flow");
|
||||
|
||||
// First login
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
helper.assert_user_is_authenticated().await?;
|
||||
|
||||
// Test logout
|
||||
helper.logout().await?;
|
||||
|
||||
// Verify logout success
|
||||
let current_url = helper.browser.get_current_url().await?;
|
||||
assert!(!current_url.contains("dashboard"));
|
||||
|
||||
// Verify user menu/authentication indicators are gone
|
||||
assert!(!helper.browser.element_exists(".user-menu, .logout-btn").await);
|
||||
|
||||
// Try to access protected page and verify redirect
|
||||
helper.browser.navigate_to("/dashboard").await?;
|
||||
|
||||
// Should redirect to login or show unauthorized
|
||||
let final_url = helper.browser.get_current_url().await?;
|
||||
assert!(final_url.contains("login") || final_url.contains("unauthorized") || !final_url.contains("dashboard"));
|
||||
|
||||
helper.take_screenshot("logout_success").await?;
|
||||
|
||||
log::info!("User logout flow test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test cart migration during login
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_cart_migration_during_login() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing cart migration during login");
|
||||
|
||||
// Step 1: Add items to cart as anonymous user
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Try to add item to anonymous cart
|
||||
if helper.browser.element_exists(".add-to-cart, .product-card").await {
|
||||
helper.browser.click(".add-to-cart, .product-card .btn").await?;
|
||||
helper.browser.wait_for_element(".cart-updated, .notification").await.ok();
|
||||
}
|
||||
|
||||
// Navigate to anonymous cart and verify items
|
||||
helper.navigate_to_cart().await?;
|
||||
let anonymous_cart_items = helper.browser.find_elements(".cart-item, .product-item").await.unwrap_or_default().len();
|
||||
|
||||
helper.take_screenshot("anonymous_cart_with_items").await?;
|
||||
|
||||
// Step 2: Login and verify cart migration
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Navigate to authenticated user cart
|
||||
helper.navigate_to_cart().await?;
|
||||
|
||||
// Verify cart items were migrated
|
||||
let authenticated_cart_items = helper.browser.find_elements(".cart-item, .product-item").await.unwrap_or_default().len();
|
||||
|
||||
// Cart should have same or more items (if user already had items)
|
||||
assert!(authenticated_cart_items >= anonymous_cart_items,
|
||||
"Cart migration failed: anonymous had {}, authenticated has {}",
|
||||
anonymous_cart_items, authenticated_cart_items);
|
||||
|
||||
helper.take_screenshot("authenticated_cart_after_migration").await?;
|
||||
|
||||
log::info!("Cart migration during login test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test session management and persistence
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_session_management() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing session management and persistence");
|
||||
|
||||
// Login
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
helper.assert_user_is_authenticated().await?;
|
||||
|
||||
// Navigate to different pages and verify session persists
|
||||
let test_pages = vec![
|
||||
"/dashboard",
|
||||
"/dashboard/wallet",
|
||||
"/marketplace",
|
||||
"/dashboard/settings",
|
||||
];
|
||||
|
||||
for page in test_pages {
|
||||
helper.browser.navigate_to(page).await?;
|
||||
|
||||
// Verify still authenticated on each page
|
||||
if page.contains("dashboard") {
|
||||
// For dashboard pages, check for dashboard elements
|
||||
helper.browser.wait_for_element(".dashboard, .user-content").await.ok();
|
||||
}
|
||||
|
||||
// Check that we're not redirected to login
|
||||
let current_url = helper.browser.get_current_url().await?;
|
||||
assert!(!current_url.contains("login"), "Session expired on page {}", page);
|
||||
|
||||
helper.take_screenshot(&format!("session_check_{}", page.replace("/", "_"))).await?;
|
||||
}
|
||||
|
||||
// Test API authentication status
|
||||
let auth_status = helper.api_client.validate_auth_status().await?;
|
||||
assert!(auth_status.authenticated, "API reports user not authenticated");
|
||||
assert_eq!(auth_status.user_email, Some(persona.email.clone()));
|
||||
|
||||
log::info!("Session management test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test login error handling
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_login_error_handling() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing login error handling");
|
||||
|
||||
// Navigate to login page
|
||||
helper.browser.navigate_to("/login").await?;
|
||||
|
||||
// Test invalid credentials
|
||||
helper.browser.type_text("input[name='email'], input[type='email']", "invalid@example.com").await?;
|
||||
helper.browser.type_text("input[name='password'], input[type='password']", "wrongpassword").await?;
|
||||
|
||||
helper.take_screenshot("login_invalid_credentials").await?;
|
||||
|
||||
// Submit login
|
||||
helper.browser.click("button[type='submit'], .login-btn").await?;
|
||||
|
||||
// Should show error message
|
||||
helper.browser.wait_for_element(".error, .alert-danger, .login-error").await?;
|
||||
|
||||
// Verify error message is displayed
|
||||
assert!(helper.browser.element_exists(".error, .alert-danger, .login-error").await);
|
||||
|
||||
// Verify user is not authenticated
|
||||
assert!(!helper.browser.element_exists(".user-menu, .dashboard-link").await);
|
||||
|
||||
helper.take_screenshot("login_error_displayed").await?;
|
||||
|
||||
// Test empty form submission
|
||||
helper.browser.navigate_to("/login").await?;
|
||||
helper.browser.click("button[type='submit'], .login-btn").await?;
|
||||
|
||||
// Should show validation errors
|
||||
helper.browser.wait_for_element(".validation-error, .required-field").await.ok();
|
||||
helper.take_screenshot("login_validation_errors").await?;
|
||||
|
||||
log::info!("Login error handling test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test GitEa OAuth integration (conditional on environment)
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_gitea_oauth_integration() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment).await?;
|
||||
|
||||
log::info!("Testing GitEa OAuth integration");
|
||||
|
||||
// Check if GitEa OAuth is enabled
|
||||
if std::env::var("GITEA_CLIENT_ID").is_ok() {
|
||||
// Navigate to login page
|
||||
helper.browser.navigate_to("/login").await?;
|
||||
|
||||
// Look for GitEa OAuth button
|
||||
if helper.browser.element_exists(".oauth-gitea, .gitea-login, a[href*='auth/gitea']").await {
|
||||
helper.take_screenshot("login_with_gitea_option").await?;
|
||||
|
||||
// Click GitEa OAuth (don't complete the flow in tests)
|
||||
// Just verify the redirect starts
|
||||
helper.browser.click(".oauth-gitea, .gitea-login, a[href*='auth/gitea']").await?;
|
||||
|
||||
// Should redirect to GitEa or show OAuth flow
|
||||
let current_url = helper.browser.get_current_url().await?;
|
||||
assert!(current_url.contains("gitea") || current_url.contains("oauth") || current_url.contains("auth"));
|
||||
|
||||
helper.take_screenshot("gitea_oauth_redirect").await?;
|
||||
|
||||
log::info!("GitEa OAuth integration verified");
|
||||
} else {
|
||||
log::info!("GitEa OAuth button not found - may not be enabled");
|
||||
}
|
||||
} else {
|
||||
log::info!("GitEa OAuth not configured - skipping integration test");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test registration validation
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_registration_validation() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment).await?;
|
||||
|
||||
log::info!("Testing registration validation");
|
||||
|
||||
// Navigate to registration page
|
||||
helper.browser.navigate_to("/register").await?;
|
||||
|
||||
// Test empty form submission
|
||||
helper.browser.click("button[type='submit'], .register-btn").await?;
|
||||
|
||||
// Should show validation errors
|
||||
helper.browser.wait_for_element(".validation-error, .required-field, .error").await.ok();
|
||||
helper.take_screenshot("registration_validation_errors").await?;
|
||||
|
||||
// Test invalid email format
|
||||
helper.browser.type_text("input[name='email'], input[type='email']", "invalid-email").await?;
|
||||
helper.browser.type_text("input[name='password'], input[type='password']", "short").await?;
|
||||
helper.browser.click("button[type='submit'], .register-btn").await?;
|
||||
|
||||
// Should show format validation errors
|
||||
helper.browser.wait_for_element(".validation-error, .email-error").await.ok();
|
||||
helper.take_screenshot("registration_format_errors").await?;
|
||||
|
||||
// Test duplicate email (if validation exists)
|
||||
let existing_persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.browser.navigate_to("/register").await?;
|
||||
helper.browser.type_text("input[name='email'], input[type='email']", &existing_persona.email).await?;
|
||||
helper.browser.type_text("input[name='password'], input[type='password']", "validpassword123").await?;
|
||||
helper.browser.type_text("input[name='name'], input[name='full_name']", "Test User").await?;
|
||||
helper.browser.click("button[type='submit'], .register-btn").await?;
|
||||
|
||||
// May show duplicate email error
|
||||
helper.browser.wait_for_element(".error, .duplicate-email").await.ok();
|
||||
helper.take_screenshot("registration_duplicate_email").await?;
|
||||
|
||||
log::info!("Registration validation test completed successfully");
|
||||
Ok(())
|
||||
}
|
17
tests/tests_archive/ux_suite/flows/mod.rs
Normal file
17
tests/tests_archive/ux_suite/flows/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
//! UX Test Flows
|
||||
//!
|
||||
//! Complete end-to-end user flow tests organized by category
|
||||
|
||||
pub mod public_access;
|
||||
pub mod authentication;
|
||||
pub mod shopping;
|
||||
pub mod dashboard;
|
||||
pub mod provider_workflows;
|
||||
pub mod settings;
|
||||
|
||||
pub use public_access::*;
|
||||
pub use authentication::*;
|
||||
pub use shopping::*;
|
||||
pub use dashboard::*;
|
||||
pub use provider_workflows::*;
|
||||
pub use settings::*;
|
317
tests/tests_archive/ux_suite/flows/public_access.rs
Normal file
317
tests/tests_archive/ux_suite/flows/public_access.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
//! Public Access Tests
|
||||
//!
|
||||
//! Tests for functionality available without authentication:
|
||||
//! - Information pages (/docs, /privacy, /terms, /about, /contact)
|
||||
//! - Anonymous marketplace browsing
|
||||
//! - Anonymous cart functionality
|
||||
//! - Search and filtering
|
||||
|
||||
use crate::utils::*;
|
||||
use crate::environment::*;
|
||||
use tokio_test;
|
||||
|
||||
/// Test all public information pages are accessible
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_public_information_pages() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing public information pages");
|
||||
|
||||
// Test each information page
|
||||
let pages = vec![
|
||||
("/docs", "Documentation"),
|
||||
("/privacy", "Privacy"),
|
||||
("/terms", "Terms"),
|
||||
("/about", "About"),
|
||||
("/contact", "Contact"),
|
||||
];
|
||||
|
||||
for (url, expected_title_part) in pages {
|
||||
log::info!("Testing page: {}", url);
|
||||
|
||||
// Navigate to page
|
||||
helper.browser.navigate_to(url).await?;
|
||||
|
||||
// Assert page loads successfully
|
||||
helper.assert_page_loaded(expected_title_part).await?;
|
||||
|
||||
// Take screenshot
|
||||
let page_name = url.trim_start_matches('/');
|
||||
helper.take_screenshot(&format!("public_page_{}", page_name)).await?;
|
||||
|
||||
// Verify page contains expected content indicators
|
||||
match url {
|
||||
"/docs" => {
|
||||
assert!(helper.browser.element_exists(".documentation, .docs-content, h1").await);
|
||||
}
|
||||
"/privacy" => {
|
||||
assert!(helper.browser.element_exists(".privacy-policy, .legal-content, h1").await);
|
||||
}
|
||||
"/terms" => {
|
||||
assert!(helper.browser.element_exists(".terms-conditions, .legal-content, h1").await);
|
||||
}
|
||||
"/about" => {
|
||||
assert!(helper.browser.element_exists(".about-content, .marketplace-info, h1").await);
|
||||
}
|
||||
"/contact" => {
|
||||
assert!(helper.browser.element_exists(".contact-info, .contact-form, h1").await);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("All public information pages test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test anonymous marketplace browsing
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_anonymous_marketplace_browsing() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing anonymous marketplace browsing");
|
||||
|
||||
// Test main marketplace page
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
helper.assert_page_loaded("Marketplace").await?;
|
||||
|
||||
// Verify marketplace loads without authentication
|
||||
assert!(helper.browser.element_exists(".marketplace, .product-grid, .category-nav").await);
|
||||
helper.take_screenshot("marketplace_anonymous").await?;
|
||||
|
||||
// Test each marketplace category
|
||||
let categories = vec![
|
||||
("/marketplace/compute", "compute resources"),
|
||||
("/marketplace/3nodes", "3nodes"),
|
||||
("/marketplace/gateways", "gateways"),
|
||||
("/marketplace/applications", "applications"),
|
||||
("/marketplace/services", "services"),
|
||||
];
|
||||
|
||||
for (url, category_name) in categories {
|
||||
log::info!("Testing marketplace category: {}", category_name);
|
||||
|
||||
helper.browser.navigate_to(url).await?;
|
||||
|
||||
// Wait for category content to load
|
||||
helper.browser.wait_for_element(".category-content, .product-list, .marketplace").await?;
|
||||
|
||||
// Verify category-specific elements exist
|
||||
match url {
|
||||
"/marketplace/compute" => {
|
||||
// Should show VM/slice options
|
||||
helper.browser.wait_for_element(".compute-resources, .vm-options, .slice-list").await.ok();
|
||||
}
|
||||
"/marketplace/3nodes" => {
|
||||
// Should show node listings
|
||||
helper.browser.wait_for_element(".node-listings, .server-list, .3node-grid").await.ok();
|
||||
}
|
||||
"/marketplace/gateways" => {
|
||||
// Should show gateway services
|
||||
helper.browser.wait_for_element(".gateway-services, .mycelium-gateways").await.ok();
|
||||
}
|
||||
"/marketplace/applications" => {
|
||||
// Should show app listings
|
||||
helper.browser.wait_for_element(".app-grid, .application-list, .published-apps").await.ok();
|
||||
}
|
||||
"/marketplace/services" => {
|
||||
// Should show service listings
|
||||
helper.browser.wait_for_element(".service-list, .professional-services").await.ok();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let category_clean = category_name.replace(" ", "_");
|
||||
helper.take_screenshot(&format!("marketplace_{}", category_clean)).await?;
|
||||
}
|
||||
|
||||
log::info!("Anonymous marketplace browsing test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test anonymous cart functionality
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_anonymous_cart_functionality() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing anonymous cart functionality");
|
||||
|
||||
// Navigate to marketplace
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Try to add item to cart (if products are available)
|
||||
if helper.browser.element_exists(".add-to-cart, .product-card").await {
|
||||
// Click first add to cart button found
|
||||
helper.browser.click(".add-to-cart, .product-card .btn-primary").await?;
|
||||
|
||||
// Should show some indication of cart update
|
||||
helper.browser.wait_for_element(".cart-updated, .notification, .alert").await.ok();
|
||||
}
|
||||
|
||||
// Navigate to anonymous cart
|
||||
helper.navigate_to_cart().await?;
|
||||
helper.assert_page_loaded("Cart").await?;
|
||||
|
||||
// Verify cart page shows for non-authenticated users
|
||||
assert!(helper.browser.element_exists(".cart, .cart-content, .shopping-cart").await);
|
||||
helper.take_screenshot("anonymous_cart").await?;
|
||||
|
||||
// Check for login prompt when trying to checkout
|
||||
if helper.browser.element_exists(".checkout-btn, .proceed-checkout").await {
|
||||
helper.browser.click(".checkout-btn, .proceed-checkout").await?;
|
||||
|
||||
// Should redirect to login or show login prompt
|
||||
helper.browser.wait_for_element(".login-form, .auth-required, .login-prompt").await.ok();
|
||||
helper.take_screenshot("checkout_login_prompt").await?;
|
||||
}
|
||||
|
||||
log::info!("Anonymous cart functionality test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test marketplace search and filtering
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_marketplace_search_and_filtering() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing marketplace search and filtering");
|
||||
|
||||
// Navigate to marketplace
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Test search functionality
|
||||
if helper.browser.element_exists(".search-input, input[type='search']").await {
|
||||
helper.browser.type_text(".search-input, input[type='search']", "test").await?;
|
||||
|
||||
// Submit search or wait for auto-search
|
||||
if helper.browser.element_exists(".search-btn, .search-submit").await {
|
||||
helper.browser.click(".search-btn, .search-submit").await?;
|
||||
}
|
||||
|
||||
// Wait for search results
|
||||
helper.browser.wait_for_element(".search-results, .product-grid, .filtered-results").await.ok();
|
||||
helper.take_screenshot("marketplace_search_results").await?;
|
||||
}
|
||||
|
||||
// Test category filtering
|
||||
if helper.browser.element_exists(".category-filter, .filter-category").await {
|
||||
let filters = helper.browser.find_elements(".category-filter option, .filter-btn").await.ok();
|
||||
|
||||
if let Some(filters) = filters {
|
||||
if filters.len() > 1 {
|
||||
// Click first filter option
|
||||
filters[1].click().await.ok();
|
||||
|
||||
// Wait for filtered results
|
||||
helper.browser.wait_for_element(".filtered-results, .category-products").await.ok();
|
||||
helper.take_screenshot("marketplace_filtered_results").await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test price filtering if available
|
||||
if helper.browser.element_exists(".price-filter, .price-range").await {
|
||||
helper.take_screenshot("marketplace_price_filter").await?;
|
||||
}
|
||||
|
||||
log::info!("Marketplace search and filtering test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test product details pages
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_product_details_pages() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing product details pages");
|
||||
|
||||
// Navigate to marketplace
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Find and click on a product (if available)
|
||||
if helper.browser.element_exists(".product-card, .product-item").await {
|
||||
helper.browser.click(".product-card, .product-item").await?;
|
||||
|
||||
// Wait for product details page
|
||||
helper.browser.wait_for_element(".product-details, .product-info, .item-details").await.ok();
|
||||
|
||||
// Verify product details elements
|
||||
let expected_elements = vec![
|
||||
".product-name, .item-title",
|
||||
".product-price, .price-info",
|
||||
".product-description, .item-description",
|
||||
];
|
||||
|
||||
for element in expected_elements {
|
||||
if helper.browser.element_exists(element).await {
|
||||
log::info!("Found product detail element: {}", element);
|
||||
}
|
||||
}
|
||||
|
||||
helper.take_screenshot("product_details_page").await?;
|
||||
|
||||
// Test product action buttons (without authentication)
|
||||
if helper.browser.element_exists(".add-to-cart").await {
|
||||
log::info!("Add to cart button available on product details");
|
||||
}
|
||||
|
||||
if helper.browser.element_exists(".buy-now").await {
|
||||
log::info!("Buy now button available on product details");
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Product details pages test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test marketplace accessibility and responsiveness
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_marketplace_accessibility() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing marketplace accessibility and responsiveness");
|
||||
|
||||
// Navigate to marketplace
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Test different viewport sizes
|
||||
let viewport_sizes = vec![
|
||||
(1920, 1080), // Desktop
|
||||
(768, 1024), // Tablet
|
||||
(375, 667), // Mobile
|
||||
];
|
||||
|
||||
for (width, height) in viewport_sizes {
|
||||
log::info!("Testing viewport size: {}x{}", width, height);
|
||||
|
||||
// Set viewport size
|
||||
helper.browser.driver.set_window_size(width, height).await?;
|
||||
|
||||
// Wait for layout to adjust
|
||||
helper.wait(std::time::Duration::from_millis(500)).await;
|
||||
|
||||
// Take screenshot for visual verification
|
||||
helper.take_screenshot(&format!("marketplace_{}x{}", width, height)).await?;
|
||||
|
||||
// Verify key elements are still accessible
|
||||
assert!(helper.browser.element_exists("body").await);
|
||||
}
|
||||
|
||||
// Reset to default size
|
||||
helper.browser.driver.set_window_size(1920, 1080).await?;
|
||||
|
||||
log::info!("Marketplace accessibility test completed successfully");
|
||||
Ok(())
|
||||
}
|
445
tests/tests_archive/ux_suite/flows/shopping.rs
Normal file
445
tests/tests_archive/ux_suite/flows/shopping.rs
Normal file
@@ -0,0 +1,445 @@
|
||||
//! Shopping Workflow Tests
|
||||
//!
|
||||
//! Tests for complete shopping experience:
|
||||
//! - Buy Now functionality
|
||||
//! - Add to Cart workflow
|
||||
//! - Checkout process
|
||||
//! - Order confirmation
|
||||
//! - Cart management (add, remove, edit quantities)
|
||||
|
||||
use crate::utils::*;
|
||||
use crate::environment::*;
|
||||
use tokio_test;
|
||||
|
||||
/// Test Buy Now complete workflow
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_buy_now_complete_workflow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing Buy Now complete workflow");
|
||||
|
||||
// Login as consumer
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Navigate to marketplace
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Look for products with Buy Now option
|
||||
if helper.browser.element_exists(".product-card, .product-item").await {
|
||||
// Get initial wallet balance
|
||||
let initial_balance = helper.api_client.get_wallet_balance().await.ok();
|
||||
|
||||
// Click on first product to get details
|
||||
helper.browser.click(".product-card, .product-item").await?;
|
||||
helper.browser.wait_for_element(".product-details, .buy-now").await.ok();
|
||||
|
||||
helper.take_screenshot("product_page_buy_now").await?;
|
||||
|
||||
// Click Buy Now button
|
||||
if helper.browser.element_exists(".buy-now, button[data-action='buy-now']").await {
|
||||
helper.browser.click(".buy-now, button[data-action='buy-now']").await?;
|
||||
|
||||
// Complete checkout flow
|
||||
let order_id = helper.complete_checkout_flow().await?;
|
||||
|
||||
// Verify order confirmation
|
||||
assert!(!order_id.is_empty());
|
||||
helper.assert_element_contains_text(".order-confirmation, .success", "success").await.ok();
|
||||
|
||||
// Check if wallet balance was deducted (if we got initial balance)
|
||||
if let Some(initial) = initial_balance {
|
||||
let final_balance = helper.api_client.get_wallet_balance().await?;
|
||||
assert!(final_balance.balance <= initial.balance, "Wallet balance should be deducted");
|
||||
}
|
||||
|
||||
helper.take_screenshot("buy_now_success").await?;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Buy Now complete workflow test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test Add to Cart workflow
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_add_to_cart_workflow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing Add to Cart workflow");
|
||||
|
||||
// Login as consumer
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Navigate to marketplace
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Initial cart should be empty or have known state
|
||||
helper.navigate_to_cart().await?;
|
||||
let initial_cart_items = helper.browser.find_elements(".cart-item, .product-item").await.unwrap_or_default().len();
|
||||
|
||||
// Go back to marketplace and add items
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Add first product to cart
|
||||
if helper.browser.element_exists(".add-to-cart, .product-card").await {
|
||||
helper.browser.click(".add-to-cart, .product-card .btn:not(.buy-now)").await?;
|
||||
|
||||
// Wait for cart update notification
|
||||
helper.browser.wait_for_element(".cart-updated, .notification, .alert-success").await.ok();
|
||||
helper.take_screenshot("item_added_to_cart").await?;
|
||||
|
||||
// Verify cart badge updated
|
||||
if helper.browser.element_exists(".cart-badge, .cart-count").await {
|
||||
let badge_text = helper.browser.get_text(".cart-badge, .cart-count").await?;
|
||||
let cart_count = badge_text.parse::<usize>().unwrap_or(0);
|
||||
assert!(cart_count > initial_cart_items, "Cart count should increase");
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate to cart and verify item
|
||||
helper.navigate_to_cart().await?;
|
||||
let final_cart_items = helper.browser.find_elements(".cart-item, .product-item").await.unwrap_or_default().len();
|
||||
assert!(final_cart_items > initial_cart_items, "Cart should have more items");
|
||||
|
||||
helper.take_screenshot("cart_with_added_items").await?;
|
||||
|
||||
log::info!("Add to Cart workflow test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test cart management (edit quantities, remove items)
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_cart_management() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing cart management");
|
||||
|
||||
// Login as consumer
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Add items to cart first
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Add multiple items if possible
|
||||
let add_to_cart_buttons = helper.browser.find_elements(".add-to-cart, .product-card .btn").await.unwrap_or_default();
|
||||
for (i, button) in add_to_cart_buttons.iter().take(2).enumerate() {
|
||||
button.click().await.ok();
|
||||
helper.wait(std::time::Duration::from_millis(500)).await;
|
||||
log::info!("Added item {} to cart", i + 1);
|
||||
}
|
||||
|
||||
// Navigate to cart
|
||||
helper.navigate_to_cart().await?;
|
||||
|
||||
// Test quantity editing (if available)
|
||||
if helper.browser.element_exists(".quantity-input, input[name='quantity']").await {
|
||||
helper.browser.type_text(".quantity-input, input[name='quantity']", "3").await?;
|
||||
|
||||
// Click update quantity button if exists
|
||||
if helper.browser.element_exists(".update-quantity, .quantity-update").await {
|
||||
helper.browser.click(".update-quantity, .quantity-update").await?;
|
||||
helper.browser.wait_for_element(".cart-updated, .notification").await.ok();
|
||||
}
|
||||
|
||||
helper.take_screenshot("cart_quantity_updated").await?;
|
||||
}
|
||||
|
||||
// Test increase/decrease quantity buttons
|
||||
if helper.browser.element_exists(".quantity-increase, .btn-plus").await {
|
||||
helper.browser.click(".quantity-increase, .btn-plus").await?;
|
||||
helper.wait(std::time::Duration::from_millis(500)).await;
|
||||
helper.take_screenshot("cart_quantity_increased").await?;
|
||||
}
|
||||
|
||||
if helper.browser.element_exists(".quantity-decrease, .btn-minus").await {
|
||||
helper.browser.click(".quantity-decrease, .btn-minus").await?;
|
||||
helper.wait(std::time::Duration::from_millis(500)).await;
|
||||
helper.take_screenshot("cart_quantity_decreased").await?;
|
||||
}
|
||||
|
||||
// Test remove item from cart
|
||||
let initial_items = helper.browser.find_elements(".cart-item, .product-item").await.unwrap_or_default().len();
|
||||
|
||||
if helper.browser.element_exists(".remove-item, .delete-item, .btn-remove").await {
|
||||
helper.browser.click(".remove-item, .delete-item, .btn-remove").await?;
|
||||
|
||||
// Wait for item removal
|
||||
helper.browser.wait_for_element(".item-removed, .notification").await.ok();
|
||||
|
||||
// Verify item count decreased
|
||||
let final_items = helper.browser.find_elements(".cart-item, .product-item").await.unwrap_or_default().len();
|
||||
assert!(final_items < initial_items, "Cart items should decrease after removal");
|
||||
|
||||
helper.take_screenshot("cart_item_removed").await?;
|
||||
}
|
||||
|
||||
// Test clear cart functionality
|
||||
if helper.browser.element_exists(".clear-cart, .empty-cart").await {
|
||||
helper.browser.click(".clear-cart, .empty-cart").await?;
|
||||
|
||||
// Handle confirmation if present
|
||||
if helper.browser.element_exists(".confirm-clear, .confirm-yes").await {
|
||||
helper.browser.click(".confirm-clear, .confirm-yes").await?;
|
||||
}
|
||||
|
||||
// Verify cart is empty
|
||||
helper.browser.wait_for_element(".cart-empty, .empty-state").await.ok();
|
||||
helper.take_screenshot("cart_cleared").await?;
|
||||
}
|
||||
|
||||
log::info!("Cart management test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test complete checkout process
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_complete_checkout_process() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
log::info!("Testing complete checkout process");
|
||||
|
||||
// Login as consumer
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Add items to cart
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
if helper.browser.element_exists(".add-to-cart, .product-card").await {
|
||||
helper.browser.click(".add-to-cart, .product-card .btn").await?;
|
||||
helper.browser.wait_for_element(".cart-updated, .notification").await.ok();
|
||||
}
|
||||
|
||||
// Navigate to cart
|
||||
helper.navigate_to_cart().await?;
|
||||
helper.take_screenshot("cart_before_checkout").await?;
|
||||
|
||||
// Get initial wallet balance for verification
|
||||
let initial_balance = helper.api_client.get_wallet_balance().await.ok();
|
||||
|
||||
// Proceed to checkout
|
||||
if helper.browser.element_exists(".checkout-btn, .proceed-checkout").await {
|
||||
helper.browser.click(".checkout-btn, .proceed-checkout").await?;
|
||||
|
||||
// Wait for checkout page/modal
|
||||
helper.browser.wait_for_element(".checkout, .checkout-form, .payment-form").await?;
|
||||
helper.take_screenshot("checkout_page").await?;
|
||||
|
||||
// Fill checkout form if required
|
||||
if helper.browser.element_exists("input[name='billing_address']").await {
|
||||
helper.browser.type_text("input[name='billing_address']", "123 Test Street").await?;
|
||||
}
|
||||
|
||||
if helper.browser.element_exists("input[name='city']").await {
|
||||
helper.browser.type_text("input[name='city']", "Test City").await?;
|
||||
}
|
||||
|
||||
// Confirm purchase
|
||||
if helper.browser.element_exists(".confirm-purchase, .place-order").await {
|
||||
helper.browser.click(".confirm-purchase, .place-order").await?;
|
||||
|
||||
// Wait for order processing
|
||||
helper.browser.wait_for_element(".processing, .order-confirmation, .success").await?;
|
||||
helper.take_screenshot("order_processing").await?;
|
||||
|
||||
// Wait for final confirmation
|
||||
helper.browser.wait_for_element(".order-success, .purchase-complete").await?;
|
||||
|
||||
// Verify order details are displayed
|
||||
if helper.browser.element_exists(".order-id, .order-number").await {
|
||||
let order_id = helper.browser.get_text(".order-id, .order-number").await?;
|
||||
assert!(!order_id.is_empty(), "Order ID should be displayed");
|
||||
log::info!("Order completed with ID: {}", order_id);
|
||||
}
|
||||
|
||||
helper.take_screenshot("checkout_success").await?;
|
||||
|
||||
// Verify wallet balance was deducted
|
||||
if let Some(initial) = initial_balance {
|
||||
let final_balance = helper.api_client.get_wallet_balance().await?;
|
||||
assert!(final_balance.balance < initial.balance, "Wallet balance should be deducted");
|
||||
log::info!("Wallet balance: {} -> {}", initial.balance, final_balance.balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Complete checkout process test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test checkout with insufficient funds
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_checkout_insufficient_funds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment).await?;
|
||||
|
||||
log::info!("Testing checkout with insufficient funds");
|
||||
|
||||
// Login as consumer
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Check current wallet balance
|
||||
let balance = helper.api_client.get_wallet_balance().await?;
|
||||
|
||||
// If balance is sufficient, we can't test insufficient funds scenario easily
|
||||
// This test would need a way to set balance to insufficient amount
|
||||
log::info!("Current balance: {} {}", balance.balance, balance.currency);
|
||||
|
||||
// Add expensive item to cart (if available) or multiple items
|
||||
helper.browser.navigate_to("/marketplace").await?;
|
||||
|
||||
// Add items to cart until we might exceed balance
|
||||
let products = helper.browser.find_elements(".product-card, .add-to-cart").await.unwrap_or_default();
|
||||
for product in products.iter().take(5) {
|
||||
product.click().await.ok();
|
||||
helper.wait(std::time::Duration::from_millis(300)).await;
|
||||
}
|
||||
|
||||
// Navigate to cart and try checkout
|
||||
helper.navigate_to_cart().await?;
|
||||
|
||||
if helper.browser.element_exists(".checkout-btn").await {
|
||||
helper.browser.click(".checkout-btn").await?;
|
||||
|
||||
// Look for insufficient funds error
|
||||
helper.browser.wait_for_element(".insufficient-funds, .payment-error, .error").await.ok();
|
||||
|
||||
if helper.browser.element_exists(".insufficient-funds, .payment-error").await {
|
||||
helper.take_screenshot("insufficient_funds_error").await?;
|
||||
log::info!("Insufficient funds error properly displayed");
|
||||
} else {
|
||||
log::info!("No insufficient funds scenario encountered");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test order history and tracking
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_order_history_and_tracking() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment).await?;
|
||||
|
||||
log::info!("Testing order history and tracking");
|
||||
|
||||
// Login as consumer
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Navigate to order history
|
||||
helper.navigate_to_dashboard_section("orders").await?;
|
||||
|
||||
// Verify orders page elements
|
||||
assert!(helper.browser.element_exists(".orders, .order-history, .order-list").await);
|
||||
helper.take_screenshot("order_history_page").await?;
|
||||
|
||||
// Check if orders exist
|
||||
if helper.browser.element_exists(".order-item, .order-row").await {
|
||||
// Click on first order for details
|
||||
helper.browser.click(".order-item, .order-row").await?;
|
||||
|
||||
// Wait for order details
|
||||
helper.browser.wait_for_element(".order-details, .order-info").await.ok();
|
||||
helper.take_screenshot("order_details_page").await?;
|
||||
|
||||
// Verify order details elements
|
||||
let expected_elements = vec![
|
||||
".order-id, .order-number",
|
||||
".order-date, .purchase-date",
|
||||
".order-status, .status",
|
||||
".order-total, .total-amount",
|
||||
];
|
||||
|
||||
for element in expected_elements {
|
||||
if helper.browser.element_exists(element).await {
|
||||
log::info!("Found order detail element: {}", element);
|
||||
}
|
||||
}
|
||||
|
||||
// Test invoice download if available
|
||||
if helper.browser.element_exists(".download-invoice, .invoice-link").await {
|
||||
helper.browser.click(".download-invoice, .invoice-link").await?;
|
||||
helper.take_screenshot("invoice_download").await?;
|
||||
}
|
||||
} else {
|
||||
log::info!("No orders found in history - this is expected for new test environment");
|
||||
}
|
||||
|
||||
log::info!("Order history and tracking test completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test different product categories checkout
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_different_product_categories_checkout() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment).await?;
|
||||
|
||||
log::info!("Testing different product categories checkout");
|
||||
|
||||
// Login as consumer
|
||||
let persona = helper.data_manager.get_persona(&UserRole::Consumer).unwrap();
|
||||
helper.login_as(persona).await?;
|
||||
|
||||
// Test different marketplace categories
|
||||
let categories = vec![
|
||||
"/marketplace/compute",
|
||||
"/marketplace/applications",
|
||||
"/marketplace/services",
|
||||
];
|
||||
|
||||
for category_url in categories {
|
||||
log::info!("Testing checkout for category: {}", category_url);
|
||||
|
||||
helper.browser.navigate_to(category_url).await?;
|
||||
|
||||
// Look for products in this category
|
||||
if helper.browser.element_exists(".product-card, .service-item, .app-item").await {
|
||||
// Try to add item to cart
|
||||
helper.browser.click(".add-to-cart, .buy-now").await.ok();
|
||||
|
||||
// Handle any category-specific checkout flows
|
||||
match category_url {
|
||||
"/marketplace/compute" => {
|
||||
// VM/Compute might have configuration options
|
||||
if helper.browser.element_exists(".vm-config, .compute-options").await {
|
||||
helper.take_screenshot("compute_configuration").await?;
|
||||
}
|
||||
}
|
||||
"/marketplace/applications" => {
|
||||
// Apps might have deployment options
|
||||
if helper.browser.element_exists(".app-config, .deployment-options").await {
|
||||
helper.take_screenshot("app_configuration").await?;
|
||||
}
|
||||
}
|
||||
"/marketplace/services" => {
|
||||
// Services might have booking/scheduling
|
||||
if helper.browser.element_exists(".service-booking, .schedule-options").await {
|
||||
helper.take_screenshot("service_booking").await?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
helper.wait(std::time::Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Different product categories checkout test completed successfully");
|
||||
Ok(())
|
||||
}
|
21
tests/tests_archive/ux_suite/mod.rs
Normal file
21
tests/tests_archive/ux_suite/mod.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! Project Mycelium UX Test Suite
|
||||
//!
|
||||
//! Complete end-to-end testing framework for validating the entire user experience
|
||||
//! as specified in the roadmap Section 9.
|
||||
//!
|
||||
//! ## Test Categories:
|
||||
//! - Public Access Tests (information pages, anonymous marketplace browsing)
|
||||
//! - Authentication Tests (registration, login, cart migration)
|
||||
//! - Shopping Tests (buy now, add to cart, checkout flows)
|
||||
//! - Dashboard Tests (user dashboard, wallet, orders, settings)
|
||||
//! - Provider Tests (farmer, app provider, service provider workflows)
|
||||
//! - Advanced Features (SSH keys, VM/Kubernetes, modal workflows)
|
||||
|
||||
pub mod environment;
|
||||
pub mod fixtures;
|
||||
pub mod flows;
|
||||
pub mod utils;
|
||||
pub mod reporting;
|
||||
|
||||
pub use environment::*;
|
||||
pub use utils::*;
|
75
tests/tests_archive/ux_suite/utils/assertions.rs
Normal file
75
tests/tests_archive/ux_suite/utils/assertions.rs
Normal 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(())
|
||||
}
|
||||
}
|
11
tests/tests_archive/ux_suite/utils/mod.rs
Normal file
11
tests/tests_archive/ux_suite/utils/mod.rs
Normal 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::*;
|
42
tests/tests_archive/ux_suite/utils/test_fixtures.rs
Normal file
42
tests/tests_archive/ux_suite/utils/test_fixtures.rs
Normal 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)
|
||||
}
|
||||
}
|
358
tests/tests_archive/ux_suite/utils/ux_test_helper.rs
Normal file
358
tests/tests_archive/ux_suite/utils/ux_test_helper.rs
Normal 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()
|
||||
}
|
||||
}
|
266
tests/tests_archive/ux_suite_main.rs
Normal file
266
tests/tests_archive/ux_suite_main.rs
Normal file
@@ -0,0 +1,266 @@
|
||||
//! Project Mycelium UX Test Suite - Main Test Runner
|
||||
//!
|
||||
//! Complete end-to-end testing framework for validating the entire user experience
|
||||
//! as specified in the roadmap Section 9.
|
||||
//!
|
||||
//! ## Usage:
|
||||
//! ```bash
|
||||
//! # Run all UX tests
|
||||
//! cargo test --test ux_suite_main --features ux_testing
|
||||
//!
|
||||
//! # Run specific test category
|
||||
//! cargo test --test ux_suite_main test_public_access --features ux_testing
|
||||
//! cargo test --test ux_suite_main test_authentication --features ux_testing
|
||||
//! cargo test --test ux_suite_main test_shopping --features ux_testing
|
||||
//! ```
|
||||
//!
|
||||
//! ## Environment Variables:
|
||||
//! - `UX_TEST_MODE`: "dev" (default), "ci", "full"
|
||||
//! - `UX_TEST_TIMEOUT`: Timeout in seconds (default: 60)
|
||||
//! - `SELENIUM_URL`: WebDriver URL (default: http://localhost:4444)
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
// Import all test modules
|
||||
mod ux_suite;
|
||||
use ux_suite::*;
|
||||
|
||||
static INIT_LOGGER: Once = Once::new();
|
||||
|
||||
/// Initialize logging for tests
|
||||
fn init_test_logging() {
|
||||
INIT_LOGGER.call_once(|| {
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.format_timestamp(Some(env_logger::fmt::TimestampPrecision::Seconds))
|
||||
.try_init()
|
||||
.ok();
|
||||
|
||||
log::info!("Project Mycelium UX Test Suite initialized");
|
||||
log::info!("Test mode: {}", std::env::var("UX_TEST_MODE").unwrap_or_else(|_| "dev".to_string()));
|
||||
});
|
||||
}
|
||||
|
||||
/// Setup test environment and run a test function
|
||||
async fn run_ux_test<F, Fut>(test_name: &str, test_fn: F) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: std::future::Future<Output = Result<(), Box<dyn std::error::Error>>>,
|
||||
{
|
||||
init_test_logging();
|
||||
|
||||
log::info!("=== Starting UX Test: {} ===", test_name);
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
let result = test_fn().await;
|
||||
let duration = start_time.elapsed();
|
||||
|
||||
match &result {
|
||||
Ok(_) => {
|
||||
log::info!("=== UX Test PASSED: {} ({:.2}s) ===", test_name, duration.as_secs_f64());
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("=== UX Test FAILED: {} ({:.2}s) - {} ===", test_name, duration.as_secs_f64(), e);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// ===== PUBLIC ACCESS TESTS =====
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_public_information_pages() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Public Information Pages", || async {
|
||||
flows::public_access::test_public_information_pages().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_anonymous_marketplace_browsing() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Anonymous Marketplace Browsing", || async {
|
||||
flows::public_access::test_anonymous_marketplace_browsing().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_anonymous_cart_functionality() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Anonymous Cart Functionality", || async {
|
||||
flows::public_access::test_anonymous_cart_functionality().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_marketplace_search_and_filtering() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Marketplace Search and Filtering", || async {
|
||||
flows::public_access::test_marketplace_search_and_filtering().await
|
||||
}).await
|
||||
}
|
||||
|
||||
// ===== AUTHENTICATION TESTS =====
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_user_registration_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("User Registration Flow", || async {
|
||||
flows::authentication::test_user_registration_flow().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_user_login_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("User Login Flow", || async {
|
||||
flows::authentication::test_user_login_flow().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_cart_migration_during_login() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Cart Migration During Login", || async {
|
||||
flows::authentication::test_cart_migration_during_login().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_session_management() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Session Management", || async {
|
||||
flows::authentication::test_session_management().await
|
||||
}).await
|
||||
}
|
||||
|
||||
// ===== SHOPPING WORKFLOW TESTS =====
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_buy_now_complete_workflow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Buy Now Complete Workflow", || async {
|
||||
flows::shopping::test_buy_now_complete_workflow().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_add_to_cart_workflow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Add to Cart Workflow", || async {
|
||||
flows::shopping::test_add_to_cart_workflow().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_cart_management() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Cart Management", || async {
|
||||
flows::shopping::test_cart_management().await
|
||||
}).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_complete_checkout_process() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_ux_test("Complete Checkout Process", || async {
|
||||
flows::shopping::test_complete_checkout_process().await
|
||||
}).await
|
||||
}
|
||||
|
||||
// ===== COMPREHENSIVE TEST SUITES =====
|
||||
|
||||
/// Run all public access tests
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_suite_public_access() -> Result<(), Box<dyn std::error::Error>> {
|
||||
init_test_logging();
|
||||
log::info!("Running Public Access Test Suite");
|
||||
|
||||
test_public_information_pages().await?;
|
||||
test_anonymous_marketplace_browsing().await?;
|
||||
test_anonymous_cart_functionality().await?;
|
||||
test_marketplace_search_and_filtering().await?;
|
||||
|
||||
log::info!("Public Access Test Suite completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run all authentication tests
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_suite_authentication() -> Result<(), Box<dyn std::error::Error>> {
|
||||
init_test_logging();
|
||||
log::info!("Running Authentication Test Suite");
|
||||
|
||||
test_user_registration_flow().await?;
|
||||
test_user_login_flow().await?;
|
||||
test_cart_migration_during_login().await?;
|
||||
test_session_management().await?;
|
||||
|
||||
log::info!("Authentication Test Suite completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run all shopping workflow tests
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_suite_shopping() -> Result<(), Box<dyn std::error::Error>> {
|
||||
init_test_logging();
|
||||
log::info!("Running Shopping Workflow Test Suite");
|
||||
|
||||
test_buy_now_complete_workflow().await?;
|
||||
test_add_to_cart_workflow().await?;
|
||||
test_cart_management().await?;
|
||||
test_complete_checkout_process().await?;
|
||||
|
||||
log::info!("Shopping Workflow Test Suite completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run all core UX tests (Phase 2 complete)
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_suite_core_ux() -> Result<(), Box<dyn std::error::Error>> {
|
||||
init_test_logging();
|
||||
log::info!("Running Complete Core UX Test Suite");
|
||||
|
||||
// Run all test suites sequentially
|
||||
test_suite_public_access().await?;
|
||||
test_suite_authentication().await?;
|
||||
test_suite_shopping().await?;
|
||||
|
||||
log::info!("Complete Core UX Test Suite completed successfully");
|
||||
log::info!("All Phase 2 UX tests passed!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performance and load testing
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn test_performance_benchmarks() -> Result<(), Box<dyn std::error::Error>> {
|
||||
init_test_logging();
|
||||
log::info!("Running Performance Benchmarks");
|
||||
|
||||
let environment = TestFixtures::setup_test_environment().await?;
|
||||
let mut helper = UXTestHelper::new(&environment);
|
||||
|
||||
// Test page load times
|
||||
let pages = vec!["/", "/marketplace", "/docs", "/login"];
|
||||
|
||||
for page in pages {
|
||||
let start = std::time::Instant::now();
|
||||
helper.browser.navigate_to(page).await?;
|
||||
let load_time = start.elapsed();
|
||||
|
||||
log::info!("Page {} loaded in {:.2}ms", page, load_time.as_millis());
|
||||
|
||||
// Assert reasonable load time (5 seconds max)
|
||||
assert!(load_time.as_secs() < 5, "Page {} took too long to load: {:.2}s", page, load_time.as_secs_f64());
|
||||
}
|
||||
|
||||
log::info!("Performance benchmarks completed successfully");
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user