Files
nym/nym-api/tests/public-api/simulation.rs
T
2025-06-04 11:28:47 +02:00

384 lines
15 KiB
Rust

use crate::utils::{base_url, make_request, validate_json_response};
use serde_json::Value;
#[tokio::test]
async fn test_simulation_epochs_endpoint() {
let base = match base_url() {
Ok(url) => url,
Err(_) => {
println!("NYM_API not set, skipping simulation API tests");
return;
}
};
let url = format!("{}/v1/simulation/epochs", base);
let response = make_request(&url).await;
match response {
Ok(res) => {
let json = validate_json_response(res).await;
match json {
Ok(data) => {
// Verify response structure
assert!(data.is_object());
assert!(data.get("epochs").is_some());
assert!(data.get("total_count").is_some());
let epochs = data.get("epochs").unwrap();
assert!(epochs.is_array());
// If there are epochs, verify their structure
if let Some(epoch_array) = epochs.as_array() {
if !epoch_array.is_empty() {
let first_epoch = &epoch_array[0];
verify_epoch_summary_structure(first_epoch);
}
}
println!("✓ Simulation epochs endpoint returned valid response");
}
Err(e) => {
println!("✗ Failed to parse JSON response: {}", e);
}
}
}
Err(_) => {
// API might not be running in simulation mode, which is fine for tests
println!("⚠ Simulation API not available (expected if not in simulation mode)");
}
}
}
#[tokio::test]
async fn test_simulation_epochs_with_pagination() {
let base = match base_url() {
Ok(url) => url,
Err(_) => return,
};
let url = format!("{}/v1/simulation/epochs?limit=5&offset=0", base);
let response = make_request(&url).await;
match response {
Ok(res) => {
let json = validate_json_response(res).await;
match json {
Ok(data) => {
assert!(data.is_object());
assert!(data.get("epochs").is_some());
assert!(data.get("total_count").is_some());
let epochs = data.get("epochs").unwrap().as_array().unwrap();
assert!(epochs.len() <= 5); // Should respect limit
println!("✓ Simulation epochs pagination works correctly");
}
Err(_) => {
println!("⚠ Simulation API not available");
}
}
}
Err(_) => {
println!("⚠ Simulation API not available");
}
}
}
#[tokio::test]
async fn test_simulation_epoch_details_structure() {
let base = match base_url() {
Ok(url) => url,
Err(_) => return,
};
// First, get a list of epochs to find a valid ID
let epochs_url = format!("{}/v1/simulation/epochs?limit=1", base);
let epochs_response = make_request(&epochs_url).await;
match epochs_response {
Ok(res) => {
let json = validate_json_response(res).await;
if let Ok(data) = json {
let epochs = data.get("epochs").unwrap().as_array().unwrap();
if !epochs.is_empty() {
let first_epoch = &epochs[0];
let epoch_id = first_epoch.get("id").unwrap().as_i64().unwrap();
// Now test the details endpoint
let details_url = format!("{}/v1/simulation/epochs/{}", base, epoch_id);
let details_response = make_request(&details_url).await;
match details_response {
Ok(res) => {
let json = validate_json_response(res).await;
match json {
Ok(data) => {
verify_epoch_details_structure(&data);
println!("✓ Simulation epoch details endpoint returned valid structure");
}
Err(e) => {
println!("✗ Failed to parse epoch details: {}", e);
}
}
}
Err(e) => {
println!("✗ Failed to fetch epoch details: {}", e);
}
}
}
}
}
Err(_) => {
println!("⚠ Simulation API not available");
}
}
}
#[tokio::test]
async fn test_simulation_comparison_endpoint() {
let base = match base_url() {
Ok(url) => url,
Err(_) => return,
};
// First, get a list of epochs to find a valid ID
let epochs_url = format!("{}/v1/simulation/epochs?limit=1", base);
let epochs_response = make_request(&epochs_url).await;
match epochs_response {
Ok(res) => {
let json = validate_json_response(res).await;
if let Ok(data) = json {
let epochs = data.get("epochs").unwrap().as_array().unwrap();
if !epochs.is_empty() {
let first_epoch = &epochs[0];
let epoch_id = first_epoch.get("id").unwrap().as_i64().unwrap();
// Test the comparison endpoint
let comparison_url = format!("{}/v1/simulation/epochs/{}/comparison", base, epoch_id);
let comparison_response = make_request(&comparison_url).await;
match comparison_response {
Ok(res) => {
let json = validate_json_response(res).await;
match json {
Ok(data) => {
verify_comparison_structure(&data);
println!("✓ Simulation comparison endpoint returned valid structure");
}
Err(e) => {
println!("✗ Failed to parse comparison data: {}", e);
}
}
}
Err(e) => {
println!("✗ Failed to fetch comparison data: {}", e);
}
}
}
}
}
Err(_) => {
println!("⚠ Simulation API not available");
}
}
}
#[tokio::test]
async fn test_simulation_export_endpoints() {
let base = match base_url() {
Ok(url) => url,
Err(_) => return,
};
// First, get a list of epochs to find a valid ID
let epochs_url = format!("{}/v1/simulation/epochs?limit=1", base);
let epochs_response = make_request(&epochs_url).await;
match epochs_response {
Ok(res) => {
let json = validate_json_response(res).await;
if let Ok(data) = json {
let epochs = data.get("epochs").unwrap().as_array().unwrap();
if !epochs.is_empty() {
let first_epoch = &epochs[0];
let epoch_id = first_epoch.get("id").unwrap().as_i64().unwrap();
// Test JSON export
let json_export_url = format!("{}/v1/simulation/epochs/{}/export?format=json", base, epoch_id);
let json_response = make_request(&json_export_url).await;
match json_response {
Ok(res) => {
assert!(res.status().is_success());
let content_type = res.headers().get("content-type");
if let Some(ct) = content_type {
assert!(ct.to_str().unwrap().contains("application/json"));
}
println!("✓ JSON export endpoint works correctly");
}
Err(e) => {
println!("✗ JSON export failed: {}", e);
}
}
// Test CSV export
let csv_export_url = format!("{}/v1/simulation/epochs/{}/export?format=csv", base, epoch_id);
let csv_response = make_request(&csv_export_url).await;
match csv_response {
Ok(res) => {
assert!(res.status().is_success());
let content_type = res.headers().get("content-type");
if let Some(ct) = content_type {
assert!(ct.to_str().unwrap().contains("text/csv"));
}
println!("✓ CSV export endpoint works correctly");
}
Err(e) => {
println!("✗ CSV export failed: {}", e);
}
}
}
}
}
Err(_) => {
println!("⚠ Simulation API not available");
}
}
}
#[tokio::test]
async fn test_simulation_error_handling() {
let base = match base_url() {
Ok(url) => url,
Err(_) => return,
};
// Test 404 for non-existent epoch
let invalid_url = format!("{}/v1/simulation/epochs/999999", base);
let response = make_request(&invalid_url).await;
match response {
Ok(res) => {
// Should get a successful response (empty or error structure)
assert!(res.status().is_success() || res.status().is_client_error());
println!("✓ Error handling works for invalid epoch IDs");
}
Err(_) => {
// This is also acceptable if the endpoint returns an error
println!("✓ Error handling works for invalid epoch IDs");
}
}
}
// Helper functions to verify response structures
fn verify_epoch_summary_structure(epoch: &Value) {
assert!(epoch.is_object());
assert!(epoch.get("id").is_some());
assert!(epoch.get("epoch_id").is_some());
assert!(epoch.get("calculation_method").is_some());
assert!(epoch.get("start_timestamp").is_some());
assert!(epoch.get("end_timestamp").is_some());
assert!(epoch.get("created_at").is_some());
assert!(epoch.get("nodes_analyzed").is_some());
assert!(epoch.get("available_methods").is_some());
// Verify types
assert!(epoch.get("id").unwrap().is_i64());
assert!(epoch.get("epoch_id").unwrap().is_u64());
assert!(epoch.get("calculation_method").unwrap().is_string());
assert!(epoch.get("nodes_analyzed").unwrap().is_u64());
assert!(epoch.get("available_methods").unwrap().is_array());
}
fn verify_epoch_details_structure(data: &Value) {
assert!(data.is_object());
assert!(data.get("epoch").is_some());
assert!(data.get("node_performance").is_some());
assert!(data.get("rewards").is_some());
assert!(data.get("route_analysis").is_some());
// Verify epoch summary structure
verify_epoch_summary_structure(data.get("epoch").unwrap());
// Verify arrays
assert!(data.get("node_performance").unwrap().is_array());
assert!(data.get("rewards").unwrap().is_array());
assert!(data.get("route_analysis").unwrap().is_array());
// If there's performance data, verify its structure
let performance_array = data.get("node_performance").unwrap().as_array().unwrap();
if !performance_array.is_empty() {
let first_performance = &performance_array[0];
verify_performance_data_structure(first_performance);
}
// If there's reward data, verify its structure
let rewards_array = data.get("rewards").unwrap().as_array().unwrap();
if !rewards_array.is_empty() {
let first_reward = &rewards_array[0];
verify_reward_data_structure(first_reward);
}
}
fn verify_performance_data_structure(performance: &Value) {
assert!(performance.is_object());
assert!(performance.get("node_id").is_some());
assert!(performance.get("node_type").is_some());
assert!(performance.get("reliability_score").is_some());
assert!(performance.get("positive_samples").is_some());
assert!(performance.get("negative_samples").is_some());
assert!(performance.get("calculation_method").is_some());
assert!(performance.get("calculated_at").is_some());
// Verify types
assert!(performance.get("node_id").unwrap().is_u64());
assert!(performance.get("node_type").unwrap().is_string());
assert!(performance.get("reliability_score").unwrap().is_f64());
assert!(performance.get("positive_samples").unwrap().is_u64());
assert!(performance.get("negative_samples").unwrap().is_u64());
assert!(performance.get("calculation_method").unwrap().is_string());
assert!(performance.get("calculated_at").unwrap().is_i64());
}
fn verify_reward_data_structure(reward: &Value) {
assert!(reward.is_object());
assert!(reward.get("node_id").is_some());
assert!(reward.get("node_type").is_some());
assert!(reward.get("calculated_reward_amount").is_some());
assert!(reward.get("reward_currency").is_some());
assert!(reward.get("calculation_method").is_some());
// Verify types
assert!(reward.get("node_id").unwrap().is_u64());
assert!(reward.get("node_type").unwrap().is_string());
assert!(reward.get("calculated_reward_amount").unwrap().is_f64());
assert!(reward.get("reward_currency").unwrap().is_string());
assert!(reward.get("calculation_method").unwrap().is_string());
}
fn verify_comparison_structure(data: &Value) {
assert!(data.is_object());
assert!(data.get("epoch_id").is_some());
assert!(data.get("simulation_epoch_id").is_some());
assert!(data.get("node_comparisons").is_some());
assert!(data.get("summary_statistics").is_some());
assert!(data.get("route_analysis_comparison").is_some());
// Verify types
assert!(data.get("epoch_id").unwrap().is_u64());
assert!(data.get("simulation_epoch_id").unwrap().is_i64());
assert!(data.get("node_comparisons").unwrap().is_array());
assert!(data.get("summary_statistics").unwrap().is_object());
assert!(data.get("route_analysis_comparison").unwrap().is_object());
// Verify summary statistics structure
let stats = data.get("summary_statistics").unwrap();
assert!(stats.get("total_nodes_compared").is_some());
assert!(stats.get("nodes_improved").is_some());
assert!(stats.get("nodes_degraded").is_some());
assert!(stats.get("average_reliability_old").is_some());
assert!(stats.get("average_reliability_new").is_some());
}