use rstest::*; use steel_decimal::{TypeConverter, ScriptAnalyzer, DecimalPrecision, ConversionError}; use steel::rvals::SteelVal; use rust_decimal::Decimal; use std::str::FromStr; // TypeConverter Tests #[rstest] #[case("123.456")] #[case("42")] #[case("-15.75")] #[case("0")] fn test_steel_val_string_to_decimal(#[case] input: &str) { let steel_val = SteelVal::StringV(input.into()); let result = TypeConverter::steel_val_to_decimal(&steel_val).unwrap(); let expected = Decimal::from_str(input).unwrap(); assert_eq!(result, expected); } #[rstest] #[case(42isize)] #[case(-15isize)] #[case(0isize)] fn test_steel_val_int_to_decimal(#[case] input: isize) { let steel_val = SteelVal::IntV(input); let result = TypeConverter::steel_val_to_decimal(&steel_val).unwrap(); let expected = Decimal::from(input); assert_eq!(result, expected); } #[rstest] #[case(42.5)] #[case(-15.75)] #[case(0.0)] fn test_steel_val_num_to_decimal(#[case] input: f64) { let steel_val = SteelVal::NumV(input); let result = TypeConverter::steel_val_to_decimal(&steel_val).unwrap(); let expected = Decimal::try_from(input).unwrap(); assert_eq!(result, expected); } #[rstest] fn test_steel_val_unsupported_type() { let steel_val = SteelVal::BoolV(true); let result = TypeConverter::steel_val_to_decimal(&steel_val); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), ConversionError::UnsupportedType(_))); } #[rstest] fn test_steel_val_invalid_decimal_string() { let steel_val = SteelVal::StringV("not_a_number".into()); let result = TypeConverter::steel_val_to_decimal(&steel_val); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), ConversionError::InvalidDecimal(_))); } #[rstest] #[case("123.456")] #[case("42")] #[case("-15.75")] #[case("0")] fn test_decimal_to_steel_val(#[case] input: &str) { let decimal = Decimal::from_str(input).unwrap(); let result = TypeConverter::decimal_to_steel_val(decimal); if let SteelVal::StringV(s) = result { assert_eq!(s.to_string(), input); } else { panic!("Expected StringV"); } } #[rstest] fn test_steel_vals_to_strings() { let vals = vec![ SteelVal::StringV("hello".into()), SteelVal::IntV(42), SteelVal::NumV(3.14), SteelVal::BoolV(true), ]; let result = TypeConverter::steel_vals_to_strings(vals).unwrap(); let expected = vec!["hello", "42", "3.14", "true"]; assert_eq!(result, expected); } #[rstest] fn test_steel_val_vector_to_string() { let inner_vals = vec![ SteelVal::StringV("a".into()), SteelVal::StringV("b".into()), SteelVal::StringV("c".into()), ]; let vector_val = SteelVal::VectorV(inner_vals.into()); let result = TypeConverter::steel_val_to_string(vector_val).unwrap(); assert_eq!(result, "a,b,c"); } #[rstest] #[case("123.456", "123.456")] #[case("42", "42")] #[case("-15.75", "-15.75")] fn test_validate_decimal_string(#[case] input: &str, #[case] expected: &str) { let result = TypeConverter::validate_decimal_string(input).unwrap(); assert_eq!(result, expected); } #[rstest] #[case("not_a_number")] #[case("")] #[case("12.34.56")] fn test_validate_decimal_string_invalid(#[case] input: &str) { let result = TypeConverter::validate_decimal_string(input); assert!(result.is_err()); } #[rstest] #[case(123.456)] #[case(-15.75)] #[case(0.0)] fn test_f64_to_decimal_string(#[case] input: f64) { let result = TypeConverter::f64_to_decimal_string(input).unwrap(); let expected = Decimal::try_from(input).unwrap().to_string(); assert_eq!(result, expected); } #[rstest] #[case(42i64, "42")] #[case(-15i64, "-15")] #[case(0i64, "0")] fn test_i64_to_decimal_string(#[case] input: i64, #[case] expected: &str) { let result = TypeConverter::i64_to_decimal_string(input); assert_eq!(result, expected); } #[rstest] #[case(42u64, "42")] #[case(0u64, "0")] #[case(1000u64, "1000")] fn test_u64_to_decimal_string(#[case] input: u64, #[case] expected: &str) { let result = TypeConverter::u64_to_decimal_string(input); assert_eq!(result, expected); } // ScriptAnalyzer Tests #[rstest] #[case("123.456", true)] #[case("42", true)] #[case("-15.75", true)] #[case("0", true)] #[case("not_a_number", false)] #[case("", false)] #[case("12.34.56", false)] fn test_is_decimal_like(#[case] input: &str, #[case] expected: bool) { let result = ScriptAnalyzer::is_decimal_like(input); assert_eq!(result, expected); } #[rstest] #[case(r#"(test "hello" "world")"#, vec!["hello", "world"])] #[case(r#"(add "1.5" "2.3")"#, vec!["1.5", "2.3"])] #[case(r#"(func)"#, vec![])] #[case(r#""single""#, vec!["single"])] fn test_extract_string_literals(#[case] input: &str, #[case] expected: Vec<&str>) { let result = ScriptAnalyzer::extract_string_literals(input); let expected: Vec = expected.into_iter().map(|s| s.to_string()).collect(); assert_eq!(result, expected); } #[rstest] #[case("(decimal-add x y)", "decimal-add", 1)] #[case("(decimal-add x y) (decimal-add a b)", "decimal-add", 2)] #[case("(decimal-mul x y)", "decimal-add", 0)] #[case("(decimal-add x (decimal-add y z))", "decimal-add", 2)] fn test_count_function_calls(#[case] script: &str, #[case] function_name: &str, #[case] expected: usize) { let result = ScriptAnalyzer::count_function_calls(script, function_name); assert_eq!(result, expected); } #[rstest] #[case("(decimal-add x y)", true)] #[case("(decimal-mul a b)", true)] #[case("(decimal-sin x)", true)] #[case("(regular-function x y)", false)] #[case("(+ x y)", false)] fn test_contains_decimal_functions(#[case] script: &str, #[case] expected: bool) { let result = ScriptAnalyzer::contains_decimal_functions(script); assert_eq!(result, expected); } // DecimalPrecision Tests #[rstest] #[case("123.456789", 2, "123.46")] #[case("123.456789", 4, "123.4568")] #[case("123.456789", 0, "123")] #[case("123", 2, "123.00")] fn test_set_precision(#[case] input: &str, #[case] precision: u32, #[case] expected: &str) { let result = DecimalPrecision::set_precision(input, precision).unwrap(); assert_eq!(result, expected); } #[rstest] #[case("123.456", 3)] #[case("123", 0)] #[case("123.000", 3)] #[case("0.001", 3)] fn test_get_decimal_places(#[case] input: &str, #[case] expected: u32) { let result = DecimalPrecision::get_decimal_places(input).unwrap(); assert_eq!(result, expected); } #[rstest] #[case("123.000", "123")] #[case("123.456", "123.456")] #[case("0.100", "0.1")] #[case("1000.000", "1000")] fn test_normalize(#[case] input: &str, #[case] expected: &str) { let result = DecimalPrecision::normalize(input).unwrap(); assert_eq!(result, expected); } #[rstest] fn test_precision_invalid_input() { let result = DecimalPrecision::set_precision("not_a_number", 2); assert!(result.is_err()); let result = DecimalPrecision::get_decimal_places("invalid"); assert!(result.is_err()); let result = DecimalPrecision::normalize("bad_input"); assert!(result.is_err()); } // Edge Cases and Error Handling #[rstest] fn test_type_converter_edge_cases() { // Empty string let result = TypeConverter::validate_decimal_string(""); assert!(result.is_err()); // Very large number let large_num = "999999999999999999999999999999"; let result = TypeConverter::validate_decimal_string(large_num); assert!(result.is_ok()); // Very small number let small_num = "0.000000000000000000000001"; let result = TypeConverter::validate_decimal_string(small_num); assert!(result.is_ok()); } #[rstest] fn test_script_analyzer_edge_cases() { // Empty script let result = ScriptAnalyzer::extract_string_literals(""); assert!(result.is_empty()); let result = ScriptAnalyzer::contains_decimal_functions(""); assert!(!result); // Script with no functions let result = ScriptAnalyzer::count_function_calls("just some text", "decimal-add"); assert_eq!(result, 0); // Script with escaped quotes let result = ScriptAnalyzer::extract_string_literals(r#"(test "hello \"world\"")"#); assert_eq!(result, vec!["hello \\\"world\\\""]); } #[rstest] fn test_conversion_error_types() { // Test InvalidDecimal error let result = TypeConverter::validate_decimal_string("invalid"); assert!(result.is_err()); if let Err(ConversionError::InvalidDecimal(msg)) = result { assert!(msg.contains("invalid")); } else { panic!("Expected InvalidDecimal error"); } // Test UnsupportedType error let steel_val = SteelVal::BoolV(true); let result = TypeConverter::steel_val_to_decimal(&steel_val); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), ConversionError::UnsupportedType(_))); // Test ConversionFailed error (using f64 that can't be converted) let result = TypeConverter::f64_to_decimal_string(f64::NAN); assert!(result.is_err()); if let Err(ConversionError::ConversionFailed(msg)) = result { assert!(msg.contains("f64 to decimal")); } else { panic!("Expected ConversionFailed error"); } } // Integration tests for utilities #[rstest] fn test_utility_integration() { // Test a complete workflow: string -> decimal -> string let input = "123.456789"; // Validate input let validated = TypeConverter::validate_decimal_string(input).unwrap(); // Set precision let precise = DecimalPrecision::set_precision(&validated, 2).unwrap(); assert_eq!(precise, "123.46"); // Check if it's decimal-like assert!(ScriptAnalyzer::is_decimal_like(&precise)); // Normalize (should be no change since already precise) let normalized = DecimalPrecision::normalize(&precise).unwrap(); assert_eq!(normalized, "123.46"); } #[rstest] fn test_complex_script_analysis() { let script = r#" (decimal-add "1.5" "2.3") (decimal-mul "result" "factor") (decimal-sin "angle") (decimal-gt "value" "threshold") "#; // Should detect decimal functions assert!(ScriptAnalyzer::contains_decimal_functions(script)); // Count specific functions assert_eq!(ScriptAnalyzer::count_function_calls(script, "decimal-add"), 1); assert_eq!(ScriptAnalyzer::count_function_calls(script, "decimal-mul"), 1); assert_eq!(ScriptAnalyzer::count_function_calls(script, "decimal-sin"), 1); assert_eq!(ScriptAnalyzer::count_function_calls(script, "decimal-gt"), 1); // Extract string literals let literals = ScriptAnalyzer::extract_string_literals(script); assert!(literals.contains(&"1.5".to_string())); assert!(literals.contains(&"2.3".to_string())); assert!(literals.contains(&"result".to_string())); }