use bevy_debugger_mcp::{
brp_client::BrpClient,
brp_messages::{DebugCommand, DebugResponse, QueryFilter, ComponentFilter, FilterOp},
config::Config,
debug_command_processor::{DebugCommandProcessor, DebugCommandRouter, DebugCommandRequest},
query_builder::{QueryBuilder, QueryValidator, QueryCostEstimator, QueryOptimizer, MAX_COMPONENTS_PER_QUERY, QUERY_PERFORMANCE_BUDGET_US},
query_builder_processor::QueryBuilderProcessor,
};
use serde_json::json;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use uuid::Uuid;
fn create_test_config() -> Config {
Config {
bevy_brp_host: "localhost".to_string(),
bevy_brp_port: 15702,
mcp_port: 3000,
}
}
fn create_test_brp_client() -> Arc<RwLock<BrpClient>> {
let config = create_test_config();
Arc::new(RwLock::new(BrpClient::new(&config)))
}
#[tokio::test]
async fn test_query_builder_fluent_interface() {
let query = QueryBuilder::new()
.with_component("Transform")
.with_component("Velocity")
.without_component("Camera")
.limit(50)
.offset(10);
assert_eq!(query.get_with_components(), &["Transform", "Velocity"]);
assert_eq!(query.get_without_components(), &["Camera"]);
assert_eq!(query.get_limit(), Some(50));
assert_eq!(query.get_offset(), Some(10));
}
#[tokio::test]
async fn test_query_builder_batch_components() {
let with_components = vec!["Transform", "Velocity", "Name"];
let without_components = vec!["Camera", "DirectionalLight"];
let query = QueryBuilder::new()
.with_components(with_components.clone())
.without_components(without_components.clone());
assert_eq!(query.get_with_components().len(), 3);
assert_eq!(query.get_without_components().len(), 2);
assert!(query.get_with_components().contains(&"Transform".to_string()));
assert!(query.get_without_components().contains(&"Camera".to_string()));
}
#[tokio::test]
async fn test_query_validation_success() {
let query = QueryBuilder::new()
.with_component("Transform")
.with_component("Velocity")
.limit(10);
let result = query.validate();
assert!(result.is_ok());
let validated = result.unwrap();
assert!(!validated.id.is_empty());
assert!(validated.filter.with.is_some());
assert_eq!(validated.filter.with.unwrap().len(), 2);
}
#[tokio::test]
async fn test_query_validation_unknown_component() {
let query = QueryBuilder::new()
.with_component("UnknownComponent123");
let result = query.validate();
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Unknown component type"));
assert!(error_msg.contains("UnknownComponent123"));
assert!(error_msg.contains("Available components")); }
#[tokio::test]
async fn test_query_validation_contradictory() {
let query = QueryBuilder::new()
.with_component("Transform")
.without_component("Transform");
let result = query.validate();
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("both required"));
assert!(error_msg.contains("excluded"));
assert!(error_msg.contains("Transform"));
}
#[tokio::test]
async fn test_query_validation_empty_query() {
let query = QueryBuilder::new();
let result = query.validate();
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("empty"));
assert!(error_msg.contains("must specify at least one"));
}
#[tokio::test]
async fn test_query_validation_too_many_components() {
let mut query = QueryBuilder::new();
for i in 0..=MAX_COMPONENTS_PER_QUERY {
query = query.with_component(format!("Component{}", i));
}
let result = query.validate();
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("too many components"));
assert!(error_msg.contains(&MAX_COMPONENTS_PER_QUERY.to_string()));
}
#[tokio::test]
async fn test_query_cost_estimation() {
let mut query = QueryBuilder::new()
.with_component("Transform")
.with_component("Velocity")
.limit(100);
let cost = query.estimate_cost();
assert!(cost.estimated_entities > 0);
assert!(cost.estimated_time_us > 0);
assert!(cost.estimated_memory > 0);
assert!(cost.estimated_entities <= 100);
}
#[tokio::test]
async fn test_query_cost_estimation_selectivity() {
let mut broad_query = QueryBuilder::new()
.with_component("Transform");
let mut specific_query = QueryBuilder::new()
.with_component("Camera");
let broad_cost = broad_query.estimate_cost();
let specific_cost = specific_query.estimate_cost();
assert!(specific_cost.estimated_entities <= broad_cost.estimated_entities);
}
#[tokio::test]
async fn test_optimization_hints_broad_query() {
let query = QueryBuilder::new()
.with_component("Transform");
let hints = query.get_optimization_hints();
assert!(!hints.is_empty());
let hints_text = hints.join(" ");
assert!(hints_text.contains("broad") || hints_text.contains("specific") || hints_text.contains("limit"));
}
#[tokio::test]
async fn test_optimization_hints_large_results() {
let query = QueryBuilder::new()
.with_component("Transform")
.with_component("GlobalTransform");
let hints = query.get_optimization_hints();
assert!(!hints.is_empty());
let hints_text = hints.join(" ");
assert!(hints_text.contains("limit") || hints_text.contains("pagination"));
}
#[tokio::test]
async fn test_optimization_hints_expensive_query() {
let query = QueryBuilder::new()
.with_component("Transform")
.with_component("GlobalTransform")
.with_component("Velocity");
let hints = query.get_optimization_hints();
let hints_text = hints.join(" ");
let should_have_performance_hint = hints_text.contains("performance") ||
hints_text.contains("budget") ||
hints_text.contains("selective");
assert!(should_have_performance_hint);
}
#[tokio::test]
async fn test_query_cache_key_deterministic() {
let mut query1 = QueryBuilder::new()
.with_component("Transform")
.with_component("Velocity")
.limit(10);
let mut query2 = QueryBuilder::new()
.with_component("Velocity")
.with_component("Transform") .limit(10);
let key1 = query1.cache_key();
let key2 = query2.cache_key();
assert_eq!(key1, key2);
}
#[tokio::test]
async fn test_query_cache_key_uniqueness() {
let mut query1 = QueryBuilder::new()
.with_component("Transform")
.limit(10);
let mut query2 = QueryBuilder::new()
.with_component("Transform")
.limit(20);
let key1 = query1.cache_key();
let key2 = query2.cache_key();
assert_ne!(key1, key2);
}
#[tokio::test]
async fn test_build_debug_command() {
let query = QueryBuilder::new()
.with_component("Transform")
.with_component("Velocity")
.limit(50);
let result = query.build();
assert!(result.is_ok());
match result.unwrap() {
DebugCommand::ExecuteQuery { query, limit, offset } => {
assert!(!query.id.is_empty());
assert_eq!(limit, Some(50));
assert_eq!(offset, None);
assert!(query.filter.with.is_some());
assert_eq!(query.filter.with.unwrap().len(), 2);
}
_ => panic!("Expected ExecuteQuery command"),
}
}
#[tokio::test]
async fn test_query_builder_processor_creation() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let validate_cmd = DebugCommand::ValidateQuery {
params: json!({"with": ["Transform"]})
};
let cost_cmd = DebugCommand::EstimateCost {
params: json!({"with": ["Transform"]})
};
let suggestions_cmd = DebugCommand::GetQuerySuggestions {
params: json!({"with": ["Transform"]})
};
let build_exec_cmd = DebugCommand::BuildAndExecuteQuery {
params: json!({"with": ["Transform"], "limit": 10})
};
assert!(processor.supports_command(&validate_cmd));
assert!(processor.supports_command(&cost_cmd));
assert!(processor.supports_command(&suggestions_cmd));
assert!(processor.supports_command(&build_exec_cmd));
}
#[tokio::test]
async fn test_processor_validate_query() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let params = json!({
"with": ["Transform", "Velocity"],
"without": ["Camera"],
"limit": 10
});
let cmd = DebugCommand::ValidateQuery { params };
let result = processor.process(cmd).await;
assert!(result.is_ok());
match result.unwrap() {
DebugResponse::QueryValidation { valid, query, errors, suggestions } => {
assert!(valid);
assert!(query.is_some());
assert!(errors.is_empty());
}
_ => panic!("Expected QueryValidation response"),
}
}
#[tokio::test]
async fn test_processor_validate_invalid_query() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let params = json!({
"with": ["UnknownComponent123"]
});
let cmd = DebugCommand::ValidateQuery { params };
let result = processor.process(cmd).await;
assert!(result.is_ok());
match result.unwrap() {
DebugResponse::QueryValidation { valid, query, errors, .. } => {
assert!(!valid);
assert!(query.is_none());
assert!(!errors.is_empty());
assert!(errors[0].contains("Unknown component"));
}
_ => panic!("Expected QueryValidation response"),
}
}
#[tokio::test]
async fn test_processor_estimate_cost() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let params = json!({
"with": ["Transform"],
"limit": 100
});
let cmd = DebugCommand::EstimateCost { params };
let result = processor.process(cmd).await;
assert!(result.is_ok());
match result.unwrap() {
DebugResponse::QueryCost { cost, performance_budget_exceeded, suggestions } => {
assert!(cost.estimated_entities > 0);
assert!(cost.estimated_time_us > 0);
assert!(cost.estimated_memory > 0);
}
_ => panic!("Expected QueryCost response"),
}
}
#[tokio::test]
async fn test_processor_get_suggestions() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let params = json!({
"with": ["Transform"] });
let cmd = DebugCommand::GetQuerySuggestions { params };
let result = processor.process(cmd).await;
assert!(result.is_ok());
match result.unwrap() {
DebugResponse::QuerySuggestions { suggestions, query_complexity } => {
assert!(!suggestions.is_empty());
assert!(query_complexity > 0);
}
_ => panic!("Expected QuerySuggestions response"),
}
}
#[tokio::test]
async fn test_processor_parameter_validation() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let invalid_cmd = DebugCommand::ValidateQuery {
params: json!("not an object")
};
let result = processor.validate(&invalid_cmd).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("must be an object"));
let invalid_with_cmd = DebugCommand::ValidateQuery {
params: json!({"with": "not an array"})
};
let result = processor.validate(&invalid_with_cmd).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("must be an array"));
}
#[tokio::test]
async fn test_processor_timing_estimates() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let validate_cmd = DebugCommand::ValidateQuery {
params: json!({"with": ["Transform"]})
};
let cost_cmd = DebugCommand::EstimateCost {
params: json!({"with": ["Transform"]})
};
let suggestions_cmd = DebugCommand::GetQuerySuggestions {
params: json!({"with": ["Transform"]})
};
let build_exec_cmd = DebugCommand::BuildAndExecuteQuery {
params: json!({"with": ["Transform"]})
};
let validate_time = processor.estimate_processing_time(&validate_cmd);
let cost_time = processor.estimate_processing_time(&cost_cmd);
let suggestions_time = processor.estimate_processing_time(&suggestions_cmd);
let build_exec_time = processor.estimate_processing_time(&build_exec_cmd);
assert!(build_exec_time > validate_time);
assert!(build_exec_time > cost_time);
assert!(validate_time <= Duration::from_millis(50));
assert!(cost_time <= Duration::from_millis(50));
assert!(suggestions_time <= Duration::from_millis(50));
assert!(build_exec_time <= Duration::from_millis(500));
}
#[tokio::test]
async fn test_processor_cache_functionality() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let params = json!({
"with": ["Transform", "Velocity"]
});
let cmd1 = DebugCommand::ValidateQuery { params: params.clone() };
let result1 = processor.process(cmd1).await;
assert!(result1.is_ok());
let cmd2 = DebugCommand::ValidateQuery { params };
let result2 = processor.process(cmd2).await;
assert!(result2.is_ok());
let stats = processor.get_cache_stats().await;
assert!(stats["validated_queries_cached"].as_u64().unwrap() > 0);
}
#[tokio::test]
async fn test_debug_command_router_integration() {
let brp_client = create_test_brp_client();
let router = DebugCommandRouter::new();
let processor = Arc::new(QueryBuilderProcessor::new(brp_client));
router.register_processor("query_builder".to_string(), processor).await;
let command = DebugCommand::ValidateQuery {
params: json!({
"with": ["Transform", "Velocity"],
"limit": 10
})
};
let request = DebugCommandRequest::new(
command,
Uuid::new_v4().to_string(),
Some(5),
);
let result = router.queue_command(request).await;
assert!(result.is_ok());
let process_result = router.process_next().await;
assert!(process_result.is_some());
match process_result.unwrap() {
Ok((correlation_id, response)) => {
assert!(!correlation_id.is_empty());
match response {
DebugResponse::QueryValidation { valid, .. } => {
assert!(valid); }
_ => panic!("Expected QueryValidation response"),
}
}
Err(e) => panic!("Processing failed: {}", e),
}
}
#[tokio::test]
async fn test_query_builder_with_component_filters() {
let filter = ComponentFilter {
component: "Transform".to_string(),
field: Some("translation.x".to_string()),
op: FilterOp::GreaterThan,
value: json!(10.0),
};
let query = QueryBuilder::new()
.with_component("Transform")
.where_component(filter);
assert_eq!(query.get_with_components(), &["Transform"]);
assert_eq!(query.get_component_filters().len(), 1);
assert_eq!(query.get_component_filters()[0].field, Some("translation.x".to_string()));
}
#[tokio::test]
async fn test_complex_query_performance() {
let mut query = QueryBuilder::new()
.with_components(vec!["Transform", "Velocity", "Name"])
.without_components(vec!["Camera", "DirectionalLight"]);
for i in 0..3 {
let filter = ComponentFilter {
component: "Transform".to_string(),
field: Some(format!("field_{}", i)),
op: FilterOp::Equal,
value: json!(format!("value_{}", i)),
};
query = query.where_component(filter);
}
let cost = query.estimate_cost();
let hints = query.get_optimization_hints();
assert!(cost.estimated_time_us > 1000);
assert!(!hints.is_empty());
let hints_text = hints.join(" ");
assert!(hints_text.contains("simplifying") || hints_text.contains("complex"));
}
#[tokio::test]
async fn test_batch_query_performance() {
let brp_client = create_test_brp_client();
let processor = QueryBuilderProcessor::new(brp_client);
let start = std::time::Instant::now();
for i in 0..10 {
let params = json!({
"with": [format!("Component{}", i % 3)], "limit": i * 10 + 10
});
let cmd = DebugCommand::ValidateQuery { params };
let result = processor.process(cmd).await;
assert!(result.is_ok());
}
let duration = start.elapsed();
assert!(duration < Duration::from_millis(1000)); }
#[tokio::test]
async fn test_pagination_recommendations() {
let query = QueryBuilder::new()
.with_component("Transform")
.with_component("GlobalTransform");
let mut cost_query = query.clone();
let cost = cost_query.estimate_cost();
let hints = query.get_optimization_hints();
if cost.estimated_entities > 1000 {
let hints_text = hints.join(" ");
assert!(hints_text.contains("limit") || hints_text.contains("pagination"));
}
}
#[tokio::test]
async fn test_query_builder_api_compatibility() {
let query = QueryBuilder::new()
.with_component("Transform")
.with_component("Velocity")
.without_component("Camera")
.limit(100)
.offset(50);
let validated = query.validate().unwrap();
let filter = &validated.filter;
assert!(filter.with.is_some());
assert!(filter.without.is_some());
assert_eq!(filter.with.as_ref().unwrap().len(), 2);
assert_eq!(filter.without.as_ref().unwrap().len(), 1);
assert!(!validated.id.is_empty());
assert!(validated.estimated_cost.estimated_entities > 0);
}
#[tokio::test]
async fn test_error_message_quality() {
let query = QueryBuilder::new()
.with_component("Transformm");
let result = query.validate();
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Unknown component"));
assert!(error_msg.contains("Transformm"));
assert!(error_msg.contains("Available components") || error_msg.contains("Transform")); }