344 lines
11 KiB
Rust
344 lines
11 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]
|
|
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<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.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()));
|
|
}
|