crucial self reference allowed
This commit is contained in:
453
server/tests/table_script/mathematical_operations_tests.rs
Normal file
453
server/tests/table_script/mathematical_operations_tests.rs
Normal file
@@ -0,0 +1,453 @@
|
||||
// tests/table_script/mathematical_operations_tests.rs
|
||||
|
||||
use crate::common::setup_isolated_db;
|
||||
use multieko2_server::table_script::handlers::post_table_script::post_table_script;
|
||||
use common::proto::multieko2::table_script::PostTableScriptRequest;
|
||||
use rstest::*;
|
||||
use serde_json::json;
|
||||
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<(&str, &str)>,
|
||||
) -> i64 {
|
||||
let column_definitions: Vec<String> = columns
|
||||
.iter()
|
||||
.map(|(name, type_def)| format!("\"{}\" {}", name, type_def))
|
||||
.collect();
|
||||
|
||||
let columns_json = json!(column_definitions);
|
||||
let indexes_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![("result", "NUMERIC(30, 15)")];
|
||||
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: Some(format!("Steel decimal {} with literals", operation)),
|
||||
};
|
||||
|
||||
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![
|
||||
("test_value", column_type),
|
||||
("result", "NUMERIC(30, 15)"),
|
||||
];
|
||||
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: Some(format!("Steel decimal {} with {} column", operation, column_type)),
|
||||
};
|
||||
|
||||
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![
|
||||
("principal", "NUMERIC(16, 2)"), // Principal amount
|
||||
("annual_rate", "NUMERIC(6, 5)"), // Interest rate
|
||||
("years", "INTEGER"), // Time period
|
||||
("compounding_periods", "INTEGER"), // Compounding frequency
|
||||
("compound_interest", "NUMERIC(20, 8)"), // 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: Some("Complex compound interest calculation".to_string()),
|
||||
};
|
||||
|
||||
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![
|
||||
("measurement_a", "NUMERIC(25, 15)"),
|
||||
("measurement_b", "NUMERIC(25, 15)"),
|
||||
("coefficient", "NUMERIC(10, 8)"),
|
||||
("scientific_result", "NUMERIC(30, 18)"),
|
||||
];
|
||||
|
||||
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: Some("High precision scientific calculation".to_string()),
|
||||
};
|
||||
|
||||
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![
|
||||
("boundary_value", numeric_type),
|
||||
("result", "NUMERIC(30, 15)"),
|
||||
];
|
||||
|
||||
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: Some(description.to_string()),
|
||||
};
|
||||
|
||||
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![
|
||||
("integer_quantity", "INTEGER"),
|
||||
("numeric_price", "NUMERIC(10, 4)"),
|
||||
("numeric_tax_rate", "NUMERIC(5, 4)"),
|
||||
("total_with_tax", "NUMERIC(15, 4)"),
|
||||
];
|
||||
|
||||
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: Some("Mixed INTEGER and NUMERIC calculation".to_string()),
|
||||
};
|
||||
|
||||
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![
|
||||
("test_value", "NUMERIC(15, 6)"),
|
||||
("result", "NUMERIC(20, 8)"),
|
||||
];
|
||||
|
||||
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: Some(description.to_string()),
|
||||
};
|
||||
|
||||
// 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![
|
||||
("value_a", "NUMERIC(10, 2)"),
|
||||
("value_b", "INTEGER"),
|
||||
("comparison_result", "BOOLEAN"),
|
||||
];
|
||||
|
||||
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: Some(format!("Comparison operation: {}", operation)),
|
||||
};
|
||||
|
||||
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![
|
||||
("x", "NUMERIC(15, 8)"),
|
||||
("y", "NUMERIC(15, 8)"),
|
||||
("z", "INTEGER"),
|
||||
("nested_result", "NUMERIC(25, 12)"),
|
||||
];
|
||||
|
||||
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: Some("Deeply nested mathematical expression".to_string()),
|
||||
};
|
||||
|
||||
let result = post_table_script(&pool, request).await;
|
||||
assert!(result.is_ok(), "Deeply nested mathematical expressions should succeed");
|
||||
}
|
||||
Reference in New Issue
Block a user