Vapora/crates/vapora-shared/tests/validation_integration.rs

353 lines
11 KiB
Rust
Raw Normal View History

2026-01-14 21:12:49 +00:00
// Integration tests for ValidationPipeline
// Demonstrates end-to-end schema validation workflow
use std::path::PathBuf;
use std::sync::Arc;
use serde_json::json;
use vapora_shared::validation::{
CompiledSchema, Contract, FieldSchema, FieldType, SchemaRegistry, SchemaSource,
ValidationPipeline,
};
/// Create a test schema for MCP tool validation
fn create_mcp_tool_schema() -> CompiledSchema {
CompiledSchema {
name: "tools/kanban_create_task".to_string(),
fields: vec![
FieldSchema {
name: "project_id".to_string(),
field_type: FieldType::String,
required: true,
contracts: vec![Contract::NonEmpty, Contract::Uuid],
default: None,
doc: Some("Project UUID".to_string()),
},
FieldSchema {
name: "title".to_string(),
field_type: FieldType::String,
required: true,
contracts: vec![
Contract::NonEmpty,
Contract::MinLength(3),
Contract::MaxLength(200),
],
default: None,
doc: Some("Task title (3-200 chars)".to_string()),
},
FieldSchema {
name: "description".to_string(),
field_type: FieldType::String,
required: false,
contracts: vec![],
default: Some(json!("")),
doc: Some("Task description".to_string()),
},
FieldSchema {
name: "priority".to_string(),
field_type: FieldType::String,
required: true,
contracts: vec![],
default: None,
doc: Some("Task priority".to_string()),
},
],
custom_contracts: vec![],
source_path: PathBuf::from("schemas/tools/kanban_create_task.ncl"),
}
}
/// Create a test schema for agent task assignment
fn create_agent_assignment_schema() -> CompiledSchema {
CompiledSchema {
name: "agents/task_assignment".to_string(),
fields: vec![
FieldSchema {
name: "role".to_string(),
field_type: FieldType::Enum(vec![
"developer".to_string(),
"reviewer".to_string(),
"architect".to_string(),
"tester".to_string(),
]),
required: true,
contracts: vec![],
default: None,
doc: Some("Agent role".to_string()),
},
FieldSchema {
name: "title".to_string(),
field_type: FieldType::String,
required: true,
contracts: vec![Contract::NonEmpty, Contract::MaxLength(500)],
default: None,
doc: Some("Task title".to_string()),
},
FieldSchema {
name: "priority".to_string(),
field_type: FieldType::Number,
required: false,
contracts: vec![Contract::Range {
min: 0.0,
max: 100.0,
}],
default: Some(json!(50)),
doc: Some("Priority score (0-100)".to_string()),
},
],
custom_contracts: vec![],
source_path: PathBuf::from("schemas/agents/task_assignment.ncl"),
}
}
#[tokio::test]
async fn test_mcp_tool_valid_input() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
// Load pre-compiled schema
let schema = create_mcp_tool_schema();
let source = SchemaSource::Compiled(schema);
let loaded_schema = registry
.load_from_source(source, "tools/kanban_create_task")
.await
.unwrap();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"project_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Implement validation pipeline",
"priority": "high"
});
let result = pipeline
.validate_against_schema(&loaded_schema, &input)
.unwrap();
assert!(result.valid, "Valid input should pass validation");
assert!(result.errors.is_empty());
assert!(result.validated_data.is_some());
// Check defaults applied
let validated = result.validated_data.unwrap();
assert_eq!(validated["description"], "");
}
#[tokio::test]
async fn test_mcp_tool_missing_required_field() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_mcp_tool_schema();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"title": "Test Task",
// Missing project_id (required)
"priority": "high"
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(!result.valid);
assert!(result.has_field_error("project_id"));
assert!(result.error_messages()[0].contains("Required field missing"));
}
#[tokio::test]
async fn test_mcp_tool_invalid_uuid() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_mcp_tool_schema();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"project_id": "not-a-valid-uuid",
"title": "Test Task",
"priority": "high"
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(!result.valid);
assert!(result.has_field_error("project_id"));
let error_msg = result.error_messages()[0].to_lowercase();
assert!(error_msg.contains("uuid") || error_msg.contains("format"));
}
#[tokio::test]
async fn test_mcp_tool_title_too_short() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_mcp_tool_schema();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"project_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "AB", // Only 2 chars, min is 3
"priority": "high"
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(!result.valid);
assert!(result.has_field_error("title"));
}
#[tokio::test]
async fn test_mcp_tool_title_too_long() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_mcp_tool_schema();
let pipeline = ValidationPipeline::new(registry);
let long_title = "A".repeat(201); // Max is 200
let input = json!({
"project_id": "550e8400-e29b-41d4-a716-446655440000",
"title": long_title,
"priority": "high"
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(!result.valid);
assert!(result.has_field_error("title"));
}
#[tokio::test]
async fn test_agent_assignment_valid_input() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_agent_assignment_schema();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"role": "developer",
"title": "Fix authentication bug"
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(result.valid);
assert!(result.errors.is_empty());
// Check default priority applied
let validated = result.validated_data.unwrap();
assert_eq!(validated["priority"], 50);
}
#[tokio::test]
async fn test_agent_assignment_invalid_role() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_agent_assignment_schema();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"role": "invalid_role", // Not in enum
"title": "Test Task"
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(!result.valid);
assert!(result.has_field_error("role"));
}
#[tokio::test]
async fn test_agent_assignment_priority_out_of_range() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_agent_assignment_schema();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"role": "developer",
"title": "Test Task",
"priority": 150 // > 100
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(!result.valid);
assert!(result.has_field_error("priority"));
}
#[tokio::test]
async fn test_strict_mode_rejects_unknown_fields() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_mcp_tool_schema();
let pipeline = ValidationPipeline::new(registry).with_strict_mode(true);
let input = json!({
"project_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Test Task",
"priority": "high",
"unknown_field": "value" // Not in schema
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(!result.valid);
assert!(result.has_field_error("unknown_field"));
}
#[tokio::test]
async fn test_non_strict_mode_allows_unknown_fields() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_mcp_tool_schema();
let pipeline = ValidationPipeline::new(registry).with_strict_mode(false);
let input = json!({
"project_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Test Task",
"priority": "high",
"unknown_field": "value" // Allowed in non-strict mode
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
// Should pass, but unknown_field won't be in validated_data
assert!(result.valid);
let validated = result.validated_data.unwrap();
assert!(!validated.as_object().unwrap().contains_key("unknown_field"));
}
#[tokio::test]
async fn test_multiple_validation_errors() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_mcp_tool_schema();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"project_id": "invalid-uuid",
"title": "AB", // Too short
"priority": "high"
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
assert!(!result.valid);
// Should have errors for both project_id and title
assert!(result.errors.len() >= 2);
assert!(result.has_field_error("project_id"));
assert!(result.has_field_error("title"));
}
#[tokio::test]
async fn test_validation_result_serialization() {
let registry = Arc::new(SchemaRegistry::new(PathBuf::from("schemas")));
let schema = create_mcp_tool_schema();
let pipeline = ValidationPipeline::new(registry);
let input = json!({
"title": "", // Empty violates NonEmpty
"priority": "high"
});
let result = pipeline.validate_against_schema(&schema, &input).unwrap();
// Serialize to JSON
let json_result = serde_json::to_string(&result).unwrap();
assert!(json_result.contains("valid"));
assert!(json_result.contains("errors"));
// Deserialize back
let _deserialized: vapora_shared::validation::ValidationResult =
serde_json::from_str(&json_result).unwrap();
}