Files
komp_ac/steel_decimal/tests/utils_tests.rs
2025-07-07 15:35:33 +02:00

331 lines
10 KiB
Rust

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]
#[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<String> = 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")]
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 = "9999999999999999999999999999";
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()));
}