// 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(); }