451 lines
17 KiB
Rust
451 lines
17 KiB
Rust
// tests/table_script/mathematical_operations_tests.rs
|
|
|
|
use crate::common::setup_isolated_db;
|
|
use server::table_script::handlers::post_table_script::post_table_script; // Fixed import
|
|
use common::proto::komp_ac::table_script::PostTableScriptRequest;
|
|
use common::proto::komp_ac::table_definition::ColumnDefinition;
|
|
use rstest::*;
|
|
use sqlx::PgPool;
|
|
|
|
/// Helper function to create a test table with specified columns
|
|
async fn create_test_table(
|
|
pool: &PgPool,
|
|
schema_id: i64,
|
|
table_name: &str,
|
|
columns: Vec<ColumnDefinition>,
|
|
) -> i64 {
|
|
let columns_json = serde_json::to_value(columns).unwrap();
|
|
let indexes_json = serde_json::json!([]);
|
|
|
|
sqlx::query_scalar!(
|
|
r#"INSERT INTO table_definitions (schema_id, table_name, columns, indexes)
|
|
VALUES ($1, $2, $3, $4) RETURNING id"#,
|
|
schema_id,
|
|
table_name,
|
|
columns_json,
|
|
indexes_json
|
|
)
|
|
.fetch_one(pool)
|
|
.await
|
|
.expect("Failed to create test table")
|
|
}
|
|
|
|
/// Helper function to get default schema ID
|
|
async fn get_default_schema_id(pool: &PgPool) -> i64 {
|
|
sqlx::query_scalar!("SELECT id FROM schemas WHERE name = 'default'")
|
|
.fetch_one(pool)
|
|
.await
|
|
.expect("Failed to get default schema ID")
|
|
}
|
|
|
|
/// Test fixture providing all mathematical operations that should work with steel_decimal
|
|
#[fixture]
|
|
#[once]
|
|
pub fn steel_decimal_operations() -> Vec<(&'static str, &'static str, &'static str)> {
|
|
vec![
|
|
// Basic arithmetic
|
|
("+", "addition", "binary"),
|
|
("-", "subtraction", "binary"),
|
|
("*", "multiplication", "binary"),
|
|
("/", "division", "binary"),
|
|
|
|
// Advanced math
|
|
("sqrt", "square_root", "unary"),
|
|
("abs", "absolute_value", "unary"),
|
|
("min", "minimum", "binary"),
|
|
("max", "maximum", "binary"),
|
|
("pow", "power", "binary"),
|
|
|
|
// Comparison operations
|
|
(">", "greater_than", "binary"),
|
|
("<", "less_than", "binary"),
|
|
("=", "equals", "binary"),
|
|
(">=", "greater_equal", "binary"),
|
|
("<=", "less_equal", "binary"),
|
|
]
|
|
}
|
|
|
|
/// Test fixture providing precision test scenarios
|
|
#[fixture]
|
|
#[once]
|
|
pub fn precision_scenarios() -> Vec<(&'static str, &'static str, &'static str)> {
|
|
vec![
|
|
("NUMERIC(5, 2)", "999.99", "Low precision"),
|
|
("NUMERIC(10, 4)", "999999.9999", "Medium precision"),
|
|
("NUMERIC(28, 15)", "9999999999999.999999999999999", "High precision"),
|
|
("NUMERIC(14, 4)", "9999999999.9999", "Currency precision"),
|
|
("NUMERIC(5, 4)", "9.9999", "Percentage precision"),
|
|
]
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::basic_addition("+", "10", "20")]
|
|
#[case::decimal_multiplication("*", "100.50", "2.5")]
|
|
#[case::high_precision_division("/", "123.456789012345", "3")]
|
|
#[case::scientific_notation("+", "1.5e-10", "2.3e-8")]
|
|
#[tokio::test]
|
|
async fn test_steel_decimal_literal_operations(
|
|
#[case] operation: &str,
|
|
#[case] value1: &str,
|
|
#[case] value2: &str,
|
|
) {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(30, 15)".to_string() }
|
|
];
|
|
let table_id = create_test_table(&pool, schema_id, "literal_test", columns).await;
|
|
|
|
let script = format!(r#"({} "{}" "{}")"#, operation, value1, value2);
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "result".to_string(),
|
|
script,
|
|
description: format!("Steel decimal {} with literals", operation), // Fixed: removed Some()
|
|
};
|
|
|
|
let result = post_table_script(&pool, request).await;
|
|
assert!(
|
|
result.is_ok(),
|
|
"Steel decimal {} with literals should succeed",
|
|
operation
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::integer_basic("INTEGER", "+")]
|
|
#[case::integer_multiplication("INTEGER", "*")]
|
|
#[case::integer_sqrt("INTEGER", "sqrt")]
|
|
#[case::numeric_basic("NUMERIC(10, 2)", "+")]
|
|
#[case::numeric_division("NUMERIC(15, 6)", "/")]
|
|
#[case::numeric_power("NUMERIC(8, 4)", "pow")]
|
|
#[case::high_precision("NUMERIC(28, 15)", "*")]
|
|
#[tokio::test]
|
|
async fn test_steel_decimal_column_operations(
|
|
#[case] column_type: &str,
|
|
#[case] operation: &str,
|
|
) {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "test_value".to_string(), field_type: column_type.to_string() },
|
|
ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(30, 15)".to_string() },
|
|
];
|
|
let table_id = create_test_table(&pool, schema_id, "column_test", columns).await;
|
|
|
|
let script = match operation {
|
|
"sqrt" | "abs" => {
|
|
format!(
|
|
r#"({} (steel_get_column "column_test" "test_value"))"#,
|
|
operation
|
|
)
|
|
}
|
|
_ => {
|
|
format!(
|
|
r#"({} (steel_get_column "column_test" "test_value") "10")"#,
|
|
operation
|
|
)
|
|
}
|
|
};
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "result".to_string(),
|
|
script,
|
|
description: format!("Steel decimal {} with {} column", operation, column_type), // Fixed: removed Some()
|
|
};
|
|
|
|
let result = post_table_script(&pool, request).await;
|
|
assert!(
|
|
result.is_ok(),
|
|
"Steel decimal {} with {} column should succeed",
|
|
operation,
|
|
column_type
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[tokio::test]
|
|
async fn test_complex_financial_calculation(
|
|
precision_scenarios: &Vec<(&'static str, &'static str, &'static str)>,
|
|
) {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
// Create a realistic financial calculation table
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "principal".to_string(), field_type: "NUMERIC(16, 2)".to_string() }, // Principal amount
|
|
ColumnDefinition { name: "annual_rate".to_string(), field_type: "NUMERIC(6, 5)".to_string() }, // Interest rate
|
|
ColumnDefinition { name: "years".to_string(), field_type: "INTEGER".to_string() }, // Time period
|
|
ColumnDefinition { name: "compounding_periods".to_string(), field_type: "INTEGER".to_string() }, // Compounding frequency
|
|
ColumnDefinition { name: "compound_interest".to_string(), field_type: "NUMERIC(20, 8)".to_string() }, // Result
|
|
];
|
|
|
|
let table_id = create_test_table(&pool, schema_id, "financial_calc", columns).await;
|
|
|
|
// Complex compound interest formula: P * (1 + r/n)^(n*t)
|
|
let compound_script = r#"
|
|
(*
|
|
(steel_get_column "financial_calc" "principal")
|
|
(pow
|
|
(+ "1"
|
|
(/ (steel_get_column "financial_calc" "annual_rate")
|
|
(steel_get_column "financial_calc" "compounding_periods")))
|
|
(* (steel_get_column "financial_calc" "years")
|
|
(steel_get_column "financial_calc" "compounding_periods"))))
|
|
"#;
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "compound_interest".to_string(),
|
|
script: compound_script.to_string(),
|
|
description: "Complex compound interest calculation".to_string(), // Fixed: removed Some()
|
|
};
|
|
|
|
let result = post_table_script(&pool, request).await;
|
|
assert!(result.is_ok(), "Complex financial calculation should succeed");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_scientific_precision_calculations() {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "measurement_a".to_string(), field_type: "NUMERIC(25, 15)".to_string() },
|
|
ColumnDefinition { name: "measurement_b".to_string(), field_type: "NUMERIC(25, 15)".to_string() },
|
|
ColumnDefinition { name: "coefficient".to_string(), field_type: "NUMERIC(10, 8)".to_string() },
|
|
ColumnDefinition { name: "scientific_result".to_string(), field_type: "NUMERIC(30, 18)".to_string() },
|
|
];
|
|
|
|
let table_id = create_test_table(&pool, schema_id, "scientific_data", columns).await;
|
|
|
|
// Complex scientific calculation with high precision
|
|
let scientific_script = r#"
|
|
(+
|
|
(sqrt (pow (steel_get_column "scientific_data" "measurement_a") "2"))
|
|
(* (steel_get_column "scientific_data" "coefficient")
|
|
(abs (- (steel_get_column "scientific_data" "measurement_b")
|
|
(steel_get_column "scientific_data" "measurement_a")))))
|
|
"#;
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "scientific_result".to_string(),
|
|
script: scientific_script.to_string(),
|
|
description: "High precision scientific calculation".to_string(), // Fixed: removed Some()
|
|
};
|
|
|
|
let result = post_table_script(&pool, request).await;
|
|
assert!(result.is_ok(), "High precision scientific calculation should succeed");
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::min_precision("NUMERIC(1, 0)", "Minimum precision")]
|
|
#[case::max_scale("NUMERIC(10, 10)", "Maximum relative scale")]
|
|
#[case::currency_standard("NUMERIC(14, 4)", "Standard currency precision")]
|
|
#[case::percentage_precision("NUMERIC(5, 4)", "Percentage precision")]
|
|
#[tokio::test]
|
|
async fn test_precision_boundary_conditions(
|
|
#[case] numeric_type: &str,
|
|
#[case] description: &str,
|
|
) {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "boundary_value".to_string(), field_type: numeric_type.to_string() },
|
|
ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(30, 15)".to_string() },
|
|
];
|
|
|
|
let table_id = create_test_table(&pool, schema_id, "boundary_test", columns).await;
|
|
|
|
let script = r#"(+ (steel_get_column "boundary_test" "boundary_value") "0.00001")"#;
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "result".to_string(),
|
|
script: script.to_string(),
|
|
description: description.to_string(), // Fixed: removed Some()
|
|
};
|
|
|
|
let result = post_table_script(&pool, request).await;
|
|
assert!(result.is_ok(), "{} should work with steel_decimal", description);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_mixed_integer_and_numeric_operations() {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "integer_quantity".to_string(), field_type: "INTEGER".to_string() },
|
|
ColumnDefinition { name: "numeric_price".to_string(), field_type: "NUMERIC(10, 4)".to_string() },
|
|
ColumnDefinition { name: "numeric_tax_rate".to_string(), field_type: "NUMERIC(5, 4)".to_string() },
|
|
ColumnDefinition { name: "total_with_tax".to_string(), field_type: "NUMERIC(15, 4)".to_string() },
|
|
];
|
|
|
|
let table_id = create_test_table(&pool, schema_id, "mixed_types_calc", columns).await;
|
|
|
|
// Calculate total with tax: (quantity * price) * (1 + tax_rate)
|
|
let mixed_script = r#"
|
|
(*
|
|
(* (steel_get_column "mixed_types_calc" "integer_quantity")
|
|
(steel_get_column "mixed_types_calc" "numeric_price"))
|
|
(+ "1" (steel_get_column "mixed_types_calc" "numeric_tax_rate")))
|
|
"#;
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "total_with_tax".to_string(),
|
|
script: mixed_script.to_string(),
|
|
description: "Mixed INTEGER and NUMERIC calculation".to_string(), // Fixed: removed Some()
|
|
};
|
|
|
|
let result = post_table_script(&pool, request).await;
|
|
assert!(result.is_ok(), "Mixed INTEGER and NUMERIC operations should succeed");
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::zero_division("/", "0", "Division by zero should be handled")]
|
|
#[case::negative_sqrt("sqrt", "-1", "Square root of negative should be handled")]
|
|
#[case::large_power("pow", "999999999", "Very large power should be handled")]
|
|
#[tokio::test]
|
|
async fn test_mathematical_edge_cases(
|
|
#[case] operation: &str,
|
|
#[case] problematic_value: &str,
|
|
#[case] description: &str,
|
|
) {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "test_value".to_string(), field_type: "NUMERIC(15, 6)".to_string() },
|
|
ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(20, 8)".to_string() },
|
|
];
|
|
|
|
let table_id = create_test_table(&pool, schema_id, "edge_case_test", columns).await;
|
|
|
|
let script = match operation {
|
|
"sqrt" => {
|
|
format!(r#"(sqrt "{}")"#, problematic_value)
|
|
}
|
|
"/" => {
|
|
format!(r#"(/ "10" "{}")"#, problematic_value)
|
|
}
|
|
"pow" => {
|
|
format!(r#"(pow "2" "{}")"#, problematic_value)
|
|
}
|
|
_ => {
|
|
format!(r#"({} "10" "{}")"#, operation, problematic_value)
|
|
}
|
|
};
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "result".to_string(),
|
|
script,
|
|
description: description.to_string(), // Fixed: removed Some()
|
|
};
|
|
|
|
// Note: These operations should either succeed (if steel_decimal handles them gracefully)
|
|
// or fail with appropriate error messages (if they're genuinely problematic)
|
|
let result = post_table_script(&pool, request).await;
|
|
|
|
// For now, we just ensure the validation doesn't crash
|
|
// The specific behavior (success vs failure) depends on steel_decimal implementation
|
|
match result {
|
|
Ok(_) => {
|
|
// Steel decimal handled the edge case gracefully
|
|
}
|
|
Err(error) => {
|
|
// Should fail with meaningful error message, not a crash
|
|
let error_msg = error.to_string();
|
|
assert!(
|
|
!error_msg.contains("panic") && !error_msg.contains("internal error"),
|
|
"Should fail gracefully, not crash: {}",
|
|
error_msg
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_comparison_operations_with_valid_types() {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "value_a".to_string(), field_type: "NUMERIC(10, 2)".to_string() },
|
|
ColumnDefinition { name: "value_b".to_string(), field_type: "INTEGER".to_string() },
|
|
ColumnDefinition { name: "comparison_result".to_string(), field_type: "BOOLEAN".to_string() },
|
|
];
|
|
|
|
let table_id = create_test_table(&pool, schema_id, "comparison_test", columns).await;
|
|
|
|
let comparison_operations = vec![">", "<", "=", ">=", "<="];
|
|
|
|
for operation in comparison_operations {
|
|
let script = format!(
|
|
r#"({} (steel_get_column "comparison_test" "value_a")
|
|
(steel_get_column "comparison_test" "value_b"))"#,
|
|
operation
|
|
);
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "comparison_result".to_string(),
|
|
script,
|
|
description: format!("Comparison operation: {}", operation), // Fixed: removed Some()
|
|
};
|
|
|
|
let result = post_table_script(&pool, request).await;
|
|
assert!(
|
|
result.is_ok(),
|
|
"Comparison operation {} should succeed with allowed types",
|
|
operation
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_nested_mathematical_expressions() {
|
|
let pool = setup_isolated_db().await;
|
|
let schema_id = get_default_schema_id(&pool).await;
|
|
|
|
let columns: Vec<ColumnDefinition> = vec![
|
|
ColumnDefinition { name: "x".to_string(), field_type: "NUMERIC(15, 8)".to_string() },
|
|
ColumnDefinition { name: "y".to_string(), field_type: "NUMERIC(15, 8)".to_string() },
|
|
ColumnDefinition { name: "z".to_string(), field_type: "INTEGER".to_string() },
|
|
ColumnDefinition { name: "nested_result".to_string(), field_type: "NUMERIC(25, 12)".to_string() },
|
|
];
|
|
|
|
let table_id = create_test_table(&pool, schema_id, "nested_calc", columns).await;
|
|
|
|
// Deeply nested expression: sqrt((x^2 + y^2) / z) + abs(x - y)
|
|
let nested_script = r#"
|
|
(+
|
|
(sqrt
|
|
(/
|
|
(+ (pow (steel_get_column "nested_calc" "x") "2")
|
|
(pow (steel_get_column "nested_calc" "y") "2"))
|
|
(steel_get_column "nested_calc" "z")))
|
|
(abs
|
|
(- (steel_get_column "nested_calc" "x")
|
|
(steel_get_column "nested_calc" "y"))))
|
|
"#;
|
|
|
|
let request = PostTableScriptRequest {
|
|
table_definition_id: table_id,
|
|
target_column: "nested_result".to_string(),
|
|
script: nested_script.to_string(),
|
|
description: "Deeply nested mathematical expression".to_string(), // Fixed: removed Some()
|
|
};
|
|
|
|
let result = post_table_script(&pool, request).await;
|
|
assert!(result.is_ok(), "Deeply nested mathematical expressions should succeed");
|
|
}
|