use serde::Deserialize; use std::fs; #[derive(Debug, Deserialize)] struct TestSuite { #[allow(dead_code)] description: String, schema: Option, // Support JSPG-style test suites with explicit types/enums/puncs types: Option, enums: Option, puncs: Option, tests: Vec, } #[derive(Debug, Deserialize)] struct TestCase { description: String, data: serde_json::Value, valid: bool, // Support explicit schema ID target for test case schema_id: Option, // Expected output for masking tests #[allow(dead_code)] expected: Option, } // use crate::validator::registry::REGISTRY; // No longer used directly for tests! use crate::validator::Validator; use serde_json::Value; pub fn deserialize_some<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { let v = Value::deserialize(deserializer)?; Ok(Some(v)) } pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> { let content = fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path)); let suite: Vec = serde_json::from_str(&content) .unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e)); if index >= suite.len() { panic!("Index {} out of bounds for file {}", index, path); } let group = &suite[index]; let mut failures = Vec::::new(); // Create Validator Instance and parse enums, types, and puncs automatically let mut validator = Validator::from_punc_definition( group.enums.as_ref(), group.types.as_ref(), group.puncs.as_ref(), ); // 3. Register root 'schemas' if present (generic test support) // Some tests use a raw 'schema' or 'schemas' field at the group level if let Some(schema_val) = &group.schema { match serde_json::from_value::(schema_val.clone()) { Ok(mut schema) => { let id_clone = schema.obj.id.clone(); if id_clone.is_some() { validator.registry.add(schema); } else { // Fallback ID if none provided in schema let id = format!("test:{}:{}", path, index); schema.obj.id = Some(id); validator.registry.add(schema); } } Err(e) => { eprintln!( "DEBUG: FAILED to deserialize group schema for index {}: {}", index, e ); } } } // 4. Run Tests for (_test_index, test) in group.tests.iter().enumerate() { let mut schema_id = test.schema_id.clone(); // If no explicit schema_id, try to infer from the single schema in the group if schema_id.is_none() { if let Some(s) = &group.schema { // If 'schema' is a single object, use its ID or "root" if let Some(obj) = s.as_object() { if let Some(id_val) = obj.get("$id") { schema_id = id_val.as_str().map(|s| s.to_string()); } } if schema_id.is_none() { schema_id = Some(format!("test:{}:{}", path, index)); } } } // Default to the first punc if present (for puncs.json style) if schema_id.is_none() { if let Some(Value::Array(puncs)) = &group.puncs { if let Some(first_punc) = puncs.first() { if let Some(Value::Array(schemas)) = first_punc.get("schemas") { if let Some(first_schema) = schemas.first() { if let Some(id) = first_schema.get("$id").and_then(|v| v.as_str()) { schema_id = Some(id.to_string()); } } } } } } if let Some(sid) = schema_id { let result = validator.validate(&sid, &test.data); let (got_valid, _errors) = match &result { Ok(res) => (res.is_valid(), &res.errors), Err(_e) => { // If we encounter an execution error (e.g. Schema Not Found), // we treat it as a test failure. (false, &vec![]) } }; if let Some(expected) = &test.expected { // Masking Test let mut data_for_mask = test.data.clone(); match validator.mask(&sid, &mut data_for_mask) { Ok(_) => { if !equals(&data_for_mask, expected) { let msg = format!( "Masking Test '{}' failed.\nExpected: {:?}\nGot: {:?}", test.description, expected, data_for_mask ); eprintln!("{}", msg); failures.push(msg); } } Err(e) => { let msg = format!( "Masking Test '{}' failed with execution error: {:?}", test.description, e ); eprintln!("{}", msg); failures.push(msg); } } } else { // Standard Validation Test if got_valid != test.valid { let error_msg = match &result { Ok(res) => format!("{:?}", res.errors), Err(e) => format!("Execution Error: {:?}", e), }; failures.push(format!( "[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {}", group.description, test.description, test.valid, got_valid, error_msg )); } } } else { failures.push(format!( "[{}] Test '{}' skipped: No schema ID found.", group.description, test.description )); } } if !failures.is_empty() { return Err(failures.join("\n")); } Ok(()) } pub fn run_test_file(path: &str) -> Result<(), String> { let content = fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path)); let suite: Vec = serde_json::from_str(&content) .unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e)); let mut failures = Vec::::new(); for (group_index, group) in suite.into_iter().enumerate() { // Create Validator Instance and parse enums, types, and puncs automatically let mut validator = Validator::from_punc_definition( group.enums.as_ref(), group.types.as_ref(), group.puncs.as_ref(), ); let unique_id = format!("test:{}:{}", path, group_index); // Register main 'schema' if present (Standard style) if let Some(ref schema_val) = group.schema { let mut schema: crate::validator::schema::Schema = serde_json::from_value(schema_val.clone()).expect("Failed to parse test schema"); // If schema has no ID, assign unique_id and use add() or manual insert? // Compiler needs ID. Registry::add needs ID. if schema.obj.id.is_none() { schema.obj.id = Some(unique_id.clone()); } validator.registry.add(schema); } for test in group.tests { // Use explicit schema_id from test, or default to unique_id let schema_id = test.schema_id.as_deref().unwrap_or(&unique_id).to_string(); let result = validator.validate(&schema_id, &test.data); if test.valid { match result { Ok(res) => { if !res.is_valid() { let msg = format!( "Test failed (expected valid): {}\nSchema: {:?}\nData: {:?}\nErrors: {:?}", test.description, group.schema, // We might need to find the actual schema used if schema_id is custom test.data, res.errors ); eprintln!("{}", msg); failures.push(msg); } } Err(e) => { let msg = format!( "Test failed (expected valid) but got execution error: {}\nSchema: {:?}\nData: {:?}\nError: {:?}", test.description, group.schema, test.data, e ); eprintln!("{}", msg); failures.push(msg); } } } else { match result { Ok(res) => { if res.is_valid() { let msg = format!( "Test failed (expected invalid): {}\nSchema: {:?}\nData: {:?}", test.description, group.schema, test.data ); eprintln!("{}", msg); failures.push(msg); } } Err(_) => { // Expected invalid, got error (which implies invalid/failure), so this is PASS. } } } } } if !failures.is_empty() { return Err(format!( "{} tests failed in file {}:\n\n{}", failures.len(), path, failures.join("\n\n") )); } Ok(()) } pub fn is_integer(v: &Value) -> bool { match v { Value::Number(n) => { n.is_i64() || n.is_u64() || n.as_f64().filter(|n| n.fract() == 0.0).is_some() } _ => false, } } /// serde_json treats 0 and 0.0 not equal. so we cannot simply use v1==v2 pub fn equals(v1: &Value, v2: &Value) -> bool { // eprintln!("Comparing {:?} with {:?}", v1, v2); match (v1, v2) { (Value::Null, Value::Null) => true, (Value::Bool(b1), Value::Bool(b2)) => b1 == b2, (Value::Number(n1), Value::Number(n2)) => { if let (Some(n1), Some(n2)) = (n1.as_u64(), n2.as_u64()) { return n1 == n2; } if let (Some(n1), Some(n2)) = (n1.as_i64(), n2.as_i64()) { return n1 == n2; } if let (Some(n1), Some(n2)) = (n1.as_f64(), n2.as_f64()) { return (n1 - n2).abs() < f64::EPSILON; } false } (Value::String(s1), Value::String(s2)) => s1 == s2, (Value::Array(arr1), Value::Array(arr2)) => { if arr1.len() != arr2.len() { return false; } arr1.iter().zip(arr2).all(|(e1, e2)| equals(e1, e2)) } (Value::Object(obj1), Value::Object(obj2)) => { if obj1.len() != obj2.len() { return false; } for (k1, v1) in obj1 { if let Some(v2) = obj2.get(k1) { if !equals(v1, v2) { return false; } } else { return false; } } true } _ => false, } }