// tests/table_script/post_scripts_integration_tests.rs #[cfg(test)] mod integration_tests { use sqlx::PgPool; use serde_json::json; use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse}; use server::table_script::handlers::post_table_script::post_table_script; /// Test utilities for table script integration testing pub struct TableScriptTestHelper { pub pool: PgPool, pub schema_id: i64, pub schema_name: String, } impl TableScriptTestHelper { pub async fn new(test_name: &str) -> Self { let database_url = std::env::var("TEST_DATABASE_URL") .unwrap_or_else(|_| "postgresql://postgres:postgres@localhost/multieko2_test".to_string()); let pool = PgPool::connect(&database_url).await.expect("Failed to connect to test database"); sqlx::migrate!("./migrations").run(&pool).await.expect("Failed to run migrations"); let schema_name = format!("test_schema_{}", test_name); let schema_id = sqlx::query_scalar!( "INSERT INTO schemas (name) VALUES ($1) RETURNING id", schema_name ) .fetch_one(&pool) .await .expect("Failed to create test schema"); Self { pool, schema_id, schema_name, } } pub async fn create_table_with_types(&self, table_name: &str, column_definitions: Vec<(&str, &str)>) -> i64 { let columns: Vec = column_definitions .iter() .map(|(name, type_def)| format!("\"{}\" {}", name, type_def)) .collect(); let columns_json = json!(columns); 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"#, self.schema_id, table_name, columns_json, indexes_json ) .fetch_one(&self.pool) .await .expect("Failed to create test table") } pub async fn create_script(&self, table_id: i64, target_column: &str, script: &str) -> Result { let request = PostTableScriptRequest { table_definition_id: table_id, target_column: target_column.to_string(), script: script.to_string(), description: "Test script".to_string(), // Fixed: removed Some() }; post_table_script(&self.pool, request).await } pub async fn cleanup(&self) { let _ = sqlx::query(&format!("DROP SCHEMA IF EXISTS \"{}\" CASCADE", self.schema_name)) .execute(&self.pool) .await; let _ = sqlx::query("DELETE FROM schemas WHERE name = $1") .bind(&self.schema_name) .execute(&self.pool) .await; } } #[tokio::test] async fn test_comprehensive_type_validation_matrix() { let helper = TableScriptTestHelper::new("type_validation_matrix").await; // Create a comprehensive table with all supported and unsupported types let table_id = helper.create_table_with_types( "comprehensive_table", vec![ // Supported types for math operations ("integer_col", "INTEGER"), ("numeric_basic", "NUMERIC(10, 2)"), ("numeric_high_precision", "NUMERIC(28, 15)"), ("numeric_currency", "NUMERIC(14, 4)"), // Supported but not for math operations ("text_col", "TEXT"), ("boolean_col", "BOOLEAN"), // Prohibited types entirely ("bigint_col", "BIGINT"), ("date_col", "DATE"), ("timestamp_col", "TIMESTAMPTZ"), // Result columns of various types ("result_integer", "INTEGER"), ("result_numeric", "NUMERIC(15, 5)"), ("result_text", "TEXT"), ] ).await; // Test matrix: [source_type, operation, expected_result] let test_matrix = vec![ // Valid mathematical operations ("integer_col", "+", "result_numeric", true), ("numeric_basic", "*", "result_numeric", true), ("numeric_high_precision", "/", "result_numeric", true), ("integer_col", "sqrt", "result_numeric", true), // Invalid mathematical operations - prohibited types in math ("text_col", "+", "result_numeric", false), ("boolean_col", "*", "result_numeric", false), ("bigint_col", "/", "result_numeric", false), ("date_col", "-", "result_numeric", false), ("timestamp_col", "+", "result_numeric", false), // Invalid target columns - prohibited types as targets ("integer_col", "+", "bigint_col", false), ("numeric_basic", "*", "date_col", false), ("integer_col", "/", "timestamp_col", false), ]; for (source_col, operation, target_col, should_succeed) in test_matrix { let script = match operation { "sqrt" => format!(r#"(sqrt (steel_get_column "comprehensive_table" "{}"))"#, source_col), _ => format!( r#"({} (steel_get_column "comprehensive_table" "{}") "10")"#, operation, source_col ), }; let result = helper.create_script(table_id, target_col, &script).await; if should_succeed { assert!( result.is_ok(), "Should succeed: {} {} -> {}", source_col, operation, target_col ); } else { assert!( result.is_err(), "Should fail: {} {} -> {}", source_col, operation, target_col ); } } helper.cleanup().await; } #[tokio::test] async fn test_steel_decimal_precision_requirements() { let helper = TableScriptTestHelper::new("precision_requirements").await; // Create table with various precision requirements let table_id = helper.create_table_with_types( "precision_table", vec![ ("low_precision", "NUMERIC(5, 2)"), // e.g., 999.99 ("medium_precision", "NUMERIC(10, 4)"), // e.g., 999999.9999 ("high_precision", "NUMERIC(28, 15)"), // Maximum PostgreSQL precision ("currency", "NUMERIC(14, 4)"), // Standard currency precision ("percentage", "NUMERIC(5, 4)"), // e.g., 0.9999 (99.99%) ("integer_val", "INTEGER"), ("result", "NUMERIC(30, 15)"), ] ).await; // Test that steel_decimal can handle various precision levels let precision_tests = vec![ ( r#"(+ (steel_get_column "precision_table" "low_precision") "0.01")"#, "Low precision addition" ), ( r#"(* (steel_get_column "precision_table" "currency") "1.0001")"#, "Currency calculation with high precision multiplier" ), ( r#"(/ (steel_get_column "precision_table" "high_precision") "3")"#, "High precision division" ), ( r#"(pow (steel_get_column "precision_table" "percentage") "2")"#, "Percentage squared" ), ( r#"(sqrt (steel_get_column "precision_table" "medium_precision"))"#, "Square root of medium precision number" ), ]; for (script, description) in precision_tests { let result = helper.create_script(table_id, "result", script).await; assert!( result.is_ok(), "Precision test should succeed: {}", description ); } helper.cleanup().await; } #[tokio::test] async fn test_complex_financial_calculations() { let helper = TableScriptTestHelper::new("financial_calculations").await; // Create a realistic financial table let table_id = helper.create_table_with_types( "financial_instruments", vec![ ("principal", "NUMERIC(16, 2)"), // Principal amount ("annual_rate", "NUMERIC(6, 5)"), // Interest rate (e.g., 0.05250) ("years", "INTEGER"), // Time period ("compounding_periods", "INTEGER"), // Compounding frequency ("fees", "NUMERIC(10, 2)"), // Transaction fees ("compound_interest", "NUMERIC(20, 8)"), // Result column ] ).await; // Complex compound interest calculation let compound_interest_script = r#" (- (* (steel_get_column "financial_instruments" "principal") (pow (+ "1" (/ (steel_get_column "financial_instruments" "annual_rate") (steel_get_column "financial_instruments" "compounding_periods"))) (* (steel_get_column "financial_instruments" "years") (steel_get_column "financial_instruments" "compounding_periods")))) (+ (steel_get_column "financial_instruments" "principal") (steel_get_column "financial_instruments" "fees"))) "#; let result = helper.create_script(table_id, "compound_interest", compound_interest_script).await; assert!(result.is_ok(), "Complex financial calculation should succeed"); helper.cleanup().await; } #[tokio::test] async fn test_scientific_notation_support() { let helper = TableScriptTestHelper::new("scientific_notation").await; let table_id = helper.create_table_with_types( "scientific_data", vec![ ("large_number", "NUMERIC(30, 10)"), ("small_number", "NUMERIC(30, 20)"), ("result", "NUMERIC(35, 25)"), ] ).await; // Test that steel_decimal can handle scientific notation in scripts let scientific_script = r#" (+ (steel_get_column "scientific_data" "large_number") (* "1.5e-10" (steel_get_column "scientific_data" "small_number"))) "#; let result = helper.create_script(table_id, "result", scientific_script).await; assert!(result.is_ok(), "Scientific notation should be supported"); helper.cleanup().await; } #[tokio::test] async fn test_script_dependencies_and_cycles() { let helper = TableScriptTestHelper::new("dependencies_cycles").await; // Create multiple tables to test dependencies let table_a_id = helper.create_table_with_types( "table_a", vec![ ("value_a", "NUMERIC(10, 2)"), ("result_a", "NUMERIC(10, 2)"), ] ).await; let table_b_id = helper.create_table_with_types( "table_b", vec![ ("value_b", "NUMERIC(10, 2)"), ("result_b", "NUMERIC(10, 2)"), ] ).await; // Create first script: table_a.result_a depends on table_b.value_b let script_a = r#"(+ (steel_get_column "table_b" "value_b") "10")"#; let result_a = helper.create_script(table_a_id, "result_a", script_a).await; assert!(result_a.is_ok(), "First dependency script should succeed"); // Try to create circular dependency: table_b.result_b depends on table_a.result_a let script_b = r#"(* (steel_get_column "table_a" "result_a") "2")"#; let result_b = helper.create_script(table_b_id, "result_b", script_b).await; // This should either succeed (if cycle detection allows this pattern) // or fail (if cycle detection is strict) // Based on the code, it should detect and prevent cycles if result_b.is_err() { let error = result_b.unwrap_err(); assert!( error.to_string().contains("cycle") || error.to_string().contains("circular"), "Circular dependency should be detected" ); } helper.cleanup().await; } #[tokio::test] async fn test_error_message_quality() { let helper = TableScriptTestHelper::new("error_messages").await; let table_id = helper.create_table_with_types( "error_test_table", vec![ ("text_field", "TEXT"), ("numeric_field", "NUMERIC(10, 2)"), ("boolean_field", "BOOLEAN"), ("bigint_field", "BIGINT"), ] ).await; // Test various error scenarios and check error message quality let error_scenarios = vec![ ( "bigint_field", "(decimal-add \"10\" \"20\")", vec!["prohibited", "BIGINT", "target"], "Targeting prohibited type should give clear error" ), ( "numeric_field", r#"(+ (steel_get_column "error_test_table" "text_field") "10")"#, vec!["mathematical", "TEXT", "operations"], "TEXT in math operations should give clear error" ), ( "numeric_field", r#"(* (steel_get_column "error_test_table" "boolean_field") "5")"#, vec!["mathematical", "BOOLEAN", "operations"], "BOOLEAN in math operations should give clear error" ), ( "numeric_field", r#"(+ (steel_get_column "nonexistent_table" "field") "10")"#, vec!["table", "does not exist", "nonexistent_table"], "Nonexistent table should give clear error" ), ( "numeric_field", r#"(+ (steel_get_column "error_test_table" "nonexistent_field") "10")"#, vec!["column", "does not exist", "nonexistent_field"], "Nonexistent column should give clear error" ), ]; for (target_column, script, expected_keywords, description) in error_scenarios { let result = helper.create_script(table_id, target_column, script).await; assert!(result.is_err(), "{}", description); let error_message = result.unwrap_err().to_string().to_lowercase(); for keyword in expected_keywords { assert!( error_message.contains(&keyword.to_lowercase()), "Error message should contain '{}' for: {}. Got: {}", keyword, description, error_message ); } } helper.cleanup().await; } #[tokio::test] async fn test_performance_with_complex_nested_expressions() { let helper = TableScriptTestHelper::new("performance_test").await; let table_id = helper.create_table_with_types( "performance_table", vec![ ("x", "NUMERIC(15, 8)"), ("y", "NUMERIC(15, 8)"), ("z", "NUMERIC(15, 8)"), ("w", "NUMERIC(15, 8)"), ("complex_result", "NUMERIC(25, 12)"), ] ).await; // Create a deeply nested mathematical expression let complex_script = r#" (+ (* (pow (steel_get_column "performance_table" "x") "3") (sqrt (steel_get_column "performance_table" "y"))) (- (/ (+ (steel_get_column "performance_table" "z") "100") (max (steel_get_column "performance_table" "w") "1")) (* "0.5" (abs (- (steel_get_column "performance_table" "x") (steel_get_column "performance_table" "y")))))) "#; let start_time = std::time::Instant::now(); let result = helper.create_script(table_id, "complex_result", complex_script).await; let duration = start_time.elapsed(); assert!(result.is_ok(), "Complex nested expression should succeed"); assert!(duration.as_millis() < 1000, "Script validation should complete within 1 second"); helper.cleanup().await; } #[tokio::test] async fn test_boundary_conditions() { let helper = TableScriptTestHelper::new("boundary_conditions").await; // Test boundary conditions for NUMERIC types let table_id = helper.create_table_with_types( "boundary_table", vec![ ("min_numeric", "NUMERIC(1, 0)"), // Minimum: single digit, no decimal ("max_numeric", "NUMERIC(1000, 999)"), // Maximum PostgreSQL allows ("zero_scale", "NUMERIC(10, 0)"), // Integer-like numeric ("max_scale", "NUMERIC(28, 28)"), // Maximum scale ("result", "NUMERIC(1000, 999)"), ] ).await; let boundary_script = r#" (+ (steel_get_column "boundary_table" "min_numeric") (steel_get_column "boundary_table" "zero_scale")) "#; let result = helper.create_script(table_id, "result", boundary_script).await; assert!(result.is_ok(), "Boundary condition numeric types should be supported"); helper.cleanup().await; } } #[cfg(test)] mod steel_decimal_integration_tests { use server::steel::server::execution::execute_script; use std::sync::Arc; use std::collections::HashMap; use sqlx::PgPool; // Fixed: added PgPool import #[tokio::test] async fn test_steel_decimal_execution_with_valid_types() { let database_url = std::env::var("TEST_DATABASE_URL") .unwrap_or_else(|_| "postgresql://postgres:postgres@localhost/multieko2_test".to_string()); let pool = Arc::new(PgPool::connect(&database_url).await.expect("Failed to connect")); // Test that steel_decimal execution works with INTEGER and NUMERIC types let mut row_data = HashMap::new(); row_data.insert("amount".to_string(), "100.50".to_string()); row_data.insert("quantity".to_string(), "5".to_string()); row_data.insert("tax_rate".to_string(), "0.0825".to_string()); let script = r#" (+ (* amount quantity) (* amount tax_rate)) "#; let result = execute_script( script.to_string(), "STRINGS", pool, 1, "test_schema".to_string(), "test_table".to_string(), row_data, ).await; assert!(result.is_ok(), "Steel decimal execution should succeed with valid numeric types"); } #[tokio::test] async fn test_steel_decimal_precision_handling() { let database_url = std::env::var("TEST_DATABASE_URL") .unwrap_or_else(|_| "postgresql://postgres:postgres@localhost/multieko2_test".to_string()); let pool = Arc::new(PgPool::connect(&database_url).await.expect("Failed to connect")); // Test high precision calculations let mut row_data = HashMap::new(); row_data.insert("precise_value".to_string(), "123.456789012345".to_string()); row_data.insert("multiplier".to_string(), "2.718281828459045".to_string()); let script = r#"(* precise_value multiplier)"#; let result = execute_script( script.to_string(), "STRINGS", pool, 1, "test_schema".to_string(), "test_table".to_string(), row_data, ).await; assert!(result.is_ok(), "Steel decimal should handle high precision calculations"); if let Ok(server::steel::server::execution::Value::Strings(values)) = result { assert!(!values.is_empty(), "Should return calculated values"); // The result should maintain precision let result_value: f64 = values[0].parse().unwrap_or(0.0); assert!(result_value > 300.0, "Calculation result should be reasonable"); } } } // Test configuration helpers #[cfg(test)] pub mod test_config { use std::sync::Once; static INIT: Once = Once::new(); pub fn setup_test_environment() { INIT.call_once(|| { // Set up test environment variables std::env::set_var("TEST_DATABASE_URL", std::env::var("TEST_DATABASE_URL") .unwrap_or_else(|_| "postgresql://postgres:postgres@localhost/multieko2_test".to_string()) ); // Initialize logging for tests if needed (removed env_logger dependency) println!("Test environment initialized"); }); } }