init projectmycelium

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

View File

@@ -0,0 +1,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,
}