// tests/table_script/post_scripts_integration_tests.rs use crate::common::setup_isolated_db; use server::table_script::handlers::post_table_script::post_table_script; use common::proto::komp_ac::table_script::{PostTableScriptRequest, TableScriptResponse}; use common::proto::komp_ac::table_definition::ColumnDefinition; use serde_json::json; use sqlx::PgPool; /// Test utilities for table script integration testing - moved to top level for shared access pub struct TableScriptTestHelper { pub pool: PgPool, pub schema_id: i64, } impl TableScriptTestHelper { pub async fn new() -> Self { let pool = setup_isolated_db().await; let schema_id = sqlx::query_scalar!("SELECT id FROM schemas WHERE name = 'default'") .fetch_one(&pool) .await .expect("Failed to get default schema ID"); Self { pool, schema_id, } } pub async fn create_table_with_types(&self, table_name: &str, column_definitions: Vec) -> i64 { let columns_json = serde_json::to_value(column_definitions).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"#, 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(), }; post_table_script(&self.pool, request).await } } #[cfg(test)] mod integration_tests { use super::*; #[tokio::test] async fn test_comprehensive_type_validation_matrix() { let helper = TableScriptTestHelper::new().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 ColumnDefinition { name: "integer_col".to_string(), field_type: "INTEGER".to_string() }, ColumnDefinition { name: "numeric_basic".to_string(), field_type: "NUMERIC(10, 2)".to_string() }, ColumnDefinition { name: "numeric_high_precision".to_string(), field_type: "NUMERIC(28, 15)".to_string() }, ColumnDefinition { name: "numeric_currency".to_string(), field_type: "NUMERIC(14, 4)".to_string() }, // Supported but not for math operations ColumnDefinition { name: "text_col".to_string(), field_type: "TEXT".to_string() }, ColumnDefinition { name: "boolean_col".to_string(), field_type: "BOOLEAN".to_string() }, // Prohibited types entirely ColumnDefinition { name: "bigint_col".to_string(), field_type: "BIGINT".to_string() }, ColumnDefinition { name: "date_col".to_string(), field_type: "DATE".to_string() }, ColumnDefinition { name: "timestamp_col".to_string(), field_type: "TIMESTAMPTZ".to_string() }, // Result columns of various types ColumnDefinition { name: "result_integer".to_string(), field_type: "INTEGER".to_string() }, ColumnDefinition { name: "result_numeric".to_string(), field_type: "NUMERIC(15, 5)".to_string() }, ColumnDefinition { name: "result_text".to_string(), field_type: "TEXT".to_string() }, ] ).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 ); } } } #[tokio::test] async fn test_steel_decimal_precision_requirements() { let helper = TableScriptTestHelper::new().await; // Create table with various precision requirements let table_id = helper.create_table_with_types( "precision_table", vec![ ColumnDefinition { name: "low_precision".to_string(), field_type: "NUMERIC(5, 2)".to_string() }, // e.g., 999.99 ColumnDefinition { name: "medium_precision".to_string(), field_type: "NUMERIC(10, 4)".to_string() }, // e.g., 999999.9999 ColumnDefinition { name: "high_precision".to_string(), field_type: "NUMERIC(28, 15)".to_string() }, // Maximum PostgreSQL precision ColumnDefinition { name: "currency".to_string(), field_type: "NUMERIC(14, 4)".to_string() }, // Standard currency precision ColumnDefinition { name: "percentage".to_string(), field_type: "NUMERIC(5, 4)".to_string() }, // e.g., 0.9999 (99.99%) ColumnDefinition { name: "integer_val".to_string(), field_type: "INTEGER".to_string() }, ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(30, 15)".to_string() }, ] ).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 ); } } #[tokio::test] async fn test_complex_financial_calculations() { let helper = TableScriptTestHelper::new().await; // Create a realistic financial table let table_id = helper.create_table_with_types( "financial_instruments", 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 (e.g., 0.05250) 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: "fees".to_string(), field_type: "NUMERIC(10, 2)".to_string() }, // Transaction fees ColumnDefinition { name: "compound_interest".to_string(), field_type: "NUMERIC(20, 8)".to_string() }, // 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"); } #[tokio::test] async fn test_scientific_notation_support() { let helper = TableScriptTestHelper::new().await; let table_id = helper.create_table_with_types( "scientific_data", vec![ ColumnDefinition { name: "large_number".to_string(), field_type: "NUMERIC(30, 10)".to_string() }, ColumnDefinition { name: "small_number".to_string(), field_type: "NUMERIC(30, 20)".to_string() }, ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(35, 25)".to_string() }, ] ).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"); } #[tokio::test] async fn test_script_dependencies_and_cycles() { let helper = TableScriptTestHelper::new().await; println!("=== DEPENDENCY TEST DEBUG START ==="); println!("Schema ID: {}", helper.schema_id); // Create multiple tables to test dependencies let table_a_id = helper.create_table_with_types( "table_a", vec![ ColumnDefinition { name: "value_a".to_string(), field_type: "NUMERIC(10, 2)".to_string() }, ColumnDefinition { name: "result_a".to_string(), field_type: "NUMERIC(10, 2)".to_string() }, ] ).await; println!("Created table_a with ID: {}", table_a_id); let table_b_id = helper.create_table_with_types( "table_b", vec![ ColumnDefinition { name: "value_b".to_string(), field_type: "NUMERIC(10, 2)".to_string() }, ColumnDefinition { name: "result_b".to_string(), field_type: "NUMERIC(10, 2)".to_string() }, ] ).await; println!("Created table_b with ID: {}", table_b_id); // Check what tables exist in the database let existing_tables = sqlx::query!( "SELECT table_name FROM table_definitions WHERE schema_id = $1", helper.schema_id ) .fetch_all(&helper.pool) .await .expect("Failed to fetch existing tables"); println!("Existing tables in schema {}: {:?}", helper.schema_id, existing_tables.iter().map(|t| &t.table_name).collect::>() ); // 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")"#; println!("Testing cross-table script: {}", script_a); let result_a: Result = helper.create_script(table_a_id, "result_a", script_a).await; match &result_a { Ok(response) => { println!("✓ Cross-table dependency succeeded! Script ID: {}", response.id); }, Err(error) => { println!("✗ Cross-table dependency failed!"); println!("Error code: {:?}", error.code()); println!("Error message: {}", error.message()); println!("Full error: {:?}", error); // Check if this is expected (cross-table not supported) or unexpected bug let error_str = error.to_string(); if error_str.contains("does not exist") || error_str.contains("not found") { println!("This appears to be a cross-table reference limitation - skipping cycle test"); println!("=== DEPENDENCY TEST DEBUG END (SKIPPED) ==="); return; // Skip the rest if cross-table isn't supported } } } assert!(result_a.is_ok(), "First dependency script should succeed, got error: {:?}", result_a.err()); // 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")"#; println!("Testing circular dependency script: {}", script_b); let result_b: Result = helper.create_script(table_b_id, "result_b", script_b).await; match &result_b { Ok(response) => { println!("⚠ Circular dependency was allowed! Script ID: {}", response.id); println!("This might indicate cycle detection is not implemented or is lenient"); }, Err(error) => { println!("✓ Circular dependency was blocked: {}", error.message()); let error_str = error.to_string(); assert!( error_str.contains("cycle") || error_str.contains("circular"), "Circular dependency should be detected with 'cycle' or 'circular' in message, got: {}", error_str ); } } println!("=== DEPENDENCY TEST DEBUG END ==="); } #[tokio::test] async fn test_error_message_quality() { let helper = TableScriptTestHelper::new().await; let table_id = helper.create_table_with_types( "error_test_table", vec![ ColumnDefinition { name: "text_field".to_string(), field_type: "TEXT".to_string() }, ColumnDefinition { name: "numeric_field".to_string(), field_type: "NUMERIC(10, 2)".to_string() }, ColumnDefinition { name: "boolean_field".to_string(), field_type: "BOOLEAN".to_string() }, ColumnDefinition { name: "bigint_field".to_string(), field_type: "BIGINT".to_string() }, ] ).await; // Test various error scenarios and check error message quality let error_scenarios = vec![ ( "bigint_field", "(+ \"10\" \"20\")", vec!["cannot create script", "BIGINT", "cannot target columns of type"], "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 ); } } } #[tokio::test] async fn test_performance_with_complex_nested_expressions() { let helper = TableScriptTestHelper::new().await; let table_id = helper.create_table_with_types( "performance_table", 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: "NUMERIC(15, 8)".to_string() }, ColumnDefinition { name: "w".to_string(), field_type: "NUMERIC(15, 8)".to_string() }, ColumnDefinition { name: "complex_result".to_string(), field_type: "NUMERIC(25, 12)".to_string() }, ] ).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"); } #[tokio::test] async fn test_boundary_conditions() { let helper = TableScriptTestHelper::new().await; // Test boundary conditions for NUMERIC types let table_id = helper.create_table_with_types( "boundary_table", vec![ ColumnDefinition { name: "min_numeric".to_string(), field_type: "NUMERIC(1, 0)".to_string() }, // Minimum: single digit, no decimal ColumnDefinition { name: "max_numeric".to_string(), field_type: "NUMERIC(1000, 999)".to_string() }, // Maximum PostgreSQL allows ColumnDefinition { name: "zero_scale".to_string(), field_type: "NUMERIC(10, 0)".to_string() }, // Integer-like numeric ColumnDefinition { name: "max_scale".to_string(), field_type: "NUMERIC(28, 28)".to_string() }, // Maximum scale ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(1000, 999)".to_string() }, ] ).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"); } } #[cfg(test)] mod steel_decimal_integration_tests { use super::*; use server::steel::server::execution::execute_script; use std::sync::Arc; use std::collections::HashMap; #[tokio::test] async fn test_steel_decimal_execution_with_valid_types() { println!("=== STEEL DECIMAL EXECUTION DEBUG START ==="); let helper = TableScriptTestHelper::new().await; let pool = Arc::new(helper.pool.clone()); println!("Schema ID: {}", helper.schema_id); // Create a proper table for the test let table_id = helper.create_table_with_types( "test_execution_table", vec![ ColumnDefinition { name: "amount".to_string(), field_type: "NUMERIC(10, 2)".to_string() }, ColumnDefinition { name: "quantity".to_string(), field_type: "INTEGER".to_string() }, ColumnDefinition { name: "tax_rate".to_string(), field_type: "NUMERIC(5, 4)".to_string() }, ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(15, 4)".to_string() }, // Add a result column ] ).await; println!("Created test table with ID: {}", table_id); // Test row data 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()); // Script with proper $-prefixed variables (steel_decimal syntax) let script = r#" (+ (* $amount $quantity) (* $amount $tax_rate)) "#; println!("Script with $-prefixed variables: {}", script); // STEP 1: Create the script via post_table_script (this transforms it) let script_creation_result = helper.create_script(table_id, "result", script).await; assert!(script_creation_result.is_ok(), "Script creation should succeed"); let script_response = script_creation_result.unwrap(); println!("✓ Script created with ID: {}", script_response.id); // STEP 2: Load the transformed script from the database let script_record = sqlx::query!( "SELECT script FROM table_scripts WHERE id = $1", script_response.id ) .fetch_one(&helper.pool) .await .expect("Should be able to load the created script"); let transformed_script = script_record.script; println!("Transformed script: {}", transformed_script); // STEP 3: Execute the transformed script let result = execute_script( transformed_script, // ✅ Now using the transformed script "STRINGS", pool.clone(), helper.schema_id, "default".to_string(), "test_execution_table".to_string(), row_data.clone(), ).await; match &result { Ok(value) => { println!("✓ Steel decimal execution succeeded!"); println!("Result: {:?}", value); }, Err(error) => { println!("✗ Steel decimal execution failed!"); println!("Error: {}", error); } } println!("=== STEEL DECIMAL EXECUTION DEBUG END ==="); assert!(result.is_ok(), "Steel decimal execution should succeed with valid numeric types, got: {:?}", result.err()); } #[tokio::test] async fn test_steel_decimal_precision_handling() { println!("=== STEEL DECIMAL PRECISION DEBUG START ==="); let helper = TableScriptTestHelper::new().await; let pool = Arc::new(helper.pool.clone()); println!("Schema ID: {}", helper.schema_id); // Create a table with high precision columns let table_id = helper.create_table_with_types( "precision_test_table", vec![ ColumnDefinition { name: "precise_value".to_string(), field_type: "NUMERIC(20, 12)".to_string() }, ColumnDefinition { name: "multiplier".to_string(), field_type: "NUMERIC(20, 12)".to_string() }, ColumnDefinition { name: "result".to_string(), field_type: "NUMERIC(25, 15)".to_string() }, // Add result column ] ).await; println!("Created precision test table with ID: {}", table_id); // 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()); // Script with proper $-prefixed variables let script = r#"(* $precise_value $multiplier)"#; println!("Precision script with $-prefixed variables: {}", script); // STEP 1: Create the script via post_table_script (this transforms it) let script_creation_result = helper.create_script(table_id, "result", script).await; assert!(script_creation_result.is_ok(), "Script creation should succeed"); let script_response = script_creation_result.unwrap(); println!("✓ Precision script created with ID: {}", script_response.id); // STEP 2: Load the transformed script from the database let script_record = sqlx::query!( "SELECT script FROM table_scripts WHERE id = $1", script_response.id ) .fetch_one(&helper.pool) .await .expect("Should be able to load the created script"); let transformed_script = script_record.script; println!("Transformed precision script: {}", transformed_script); // STEP 3: Execute the transformed script let result = execute_script( transformed_script, // ✅ Now using the transformed script "STRINGS", pool.clone(), helper.schema_id, "default".to_string(), "precision_test_table".to_string(), row_data, ).await; match &result { Ok(value) => { println!("✓ Steel decimal precision test succeeded!"); match value { server::steel::server::execution::Value::Strings(values) => { println!("String results: {:?}", values); if !values.is_empty() { if let Ok(result_value) = values[0].parse::() { println!("Parsed result: {}", result_value); println!("Expected > 300.0: {}", result_value > 300.0); } } }, other => println!("Non-string result: {:?}", other), } }, Err(error) => { println!("✗ Steel decimal precision test failed!"); println!("Error: {}", error); } } println!("=== STEEL DECIMAL PRECISION DEBUG END ==="); assert!(result.is_ok(), "Steel decimal should handle high precision calculations, got: {:?}", result.err()); 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, got: {}", result_value); } } }