// Additional edge case tests for decimal handling // Add these to your test files #[rstest] #[tokio::test] async fn test_decimal_negative_values_rejected(#[future] pool: PgPool) { let pool = pool.await; let negative_cases = vec![ "decimal(-1, 0)", // negative precision "decimal(5, -1)", // negative scale "decimal(-5, -2)", // both negative ]; for negative_type in negative_cases { let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: format!("table_neg_{}", negative_type.replace(['(', ')', ',', '-'], "_")), columns: vec![ColumnDefinition { name: "amount".into(), field_type: negative_type.into(), }], ..Default::default() }; let result = post_table_definition(&pool, request).await; assert!(result.is_err(), "Negative values should be rejected: {}", negative_type); assert_eq!(result.unwrap_err().code(), Code::InvalidArgument); } } #[rstest] #[tokio::test] async fn test_decimal_postgresql_limits(#[future] pool: PgPool) { let pool = pool.await; // Test maximum valid PostgreSQL precision (1000) let max_valid_request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "max_precision_test".into(), columns: vec![ColumnDefinition { name: "amount".into(), field_type: "decimal(1000, 0)".into(), }], ..Default::default() }; // This should succeed (though you might want to add a limit in your code) let result = post_table_definition(&pool, max_valid_request).await; // Note: Currently your code doesn't enforce PostgreSQL's 1000 limit, // so this will succeed. You may want to add that validation. // Test over PostgreSQL limit let over_limit_request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "over_precision_test".into(), columns: vec![ColumnDefinition { name: "amount".into(), field_type: "decimal(1001, 0)".into(), }], ..Default::default() }; // This might succeed in your validation but fail at PostgreSQL level // Consider adding explicit validation for this let _result = post_table_definition(&pool, over_limit_request).await; } #[rstest] #[tokio::test] async fn test_decimal_leading_zeros_and_signs(#[future] pool: PgPool) { let pool = pool.await; let cases = vec![ ("decimal(01, 02)", false), // leading zeros - should be REJECTED ("decimal(001, 000)", false), // multiple leading zeros - should be REJECTED ("decimal(+5, +2)", false), // explicit positive signs - should be REJECTED ("decimal(05, +2)", false), // mixed formats - should be REJECTED ("decimal(2, 1)", true), // clean format - should succeed ("decimal(10, 0)", true), // clean format - should succeed ("decimal(5, 5)", true), // scale equals precision - should succeed ("decimal(1, 0)", true), // minimum valid case - should succeed ]; for (i, (field_type, should_succeed)) in cases.into_iter().enumerate() { let request = PostTableDefinitionRequest { profile_name: "default".into(), // Use completely independent, valid table names table_name: format!("test_table_{}", i), columns: vec![ColumnDefinition { name: "amount".into(), field_type: field_type.into(), }], ..Default::default() }; let result = post_table_definition(&pool, request).await; if should_succeed { assert!(result.is_ok(), "Should succeed: '{}' but got error: {:?}", field_type, result.as_ref().err().map(|e| e.message())); } else { assert!(result.is_err(), "Should fail: '{}'", field_type); if let Err(status) = result { assert_eq!(status.code(), Code::InvalidArgument, "Wrong error code for case '{}': {}", field_type, status.message()); } } } } #[rstest] #[tokio::test] async fn test_decimal_extra_parameters_rejected(#[future] pool: PgPool) { let pool = pool.await; let invalid_cases = vec![ "decimal(5,2,3)", // too many parameters "decimal(5,,2)", // double comma "decimal(5, 2, )", // trailing comma "decimal(5,2,)", // trailing comma variant "decimal(5,2,3,4)", // way too many parameters ]; for invalid_case in invalid_cases { let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: format!("table_{}", invalid_case.replace(['(', ')', ','], "_")), columns: vec![ColumnDefinition { name: "amount".into(), field_type: invalid_case.into(), }], ..Default::default() }; let result = post_table_definition(&pool, request).await; assert!(result.is_err(), "Should reject extra parameters: {}", invalid_case); assert_eq!(result.unwrap_err().code(), Code::InvalidArgument); } } #[rstest] #[tokio::test] async fn test_decimal_floating_point_inputs_rejected(#[future] pool: PgPool) { let pool = pool.await; let floating_cases = vec![ "decimal(5.5, 2)", // floating point precision "decimal(5, 2.0)", // floating point scale "decimal(5.1, 2.9)", // both floating point "decimal(1.0, 0.0)", // explicit decimals ]; for floating_case in floating_cases { let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: format!("table_{}", floating_case.replace(['(', ')', ',', '.'], "_")), columns: vec![ColumnDefinition { name: "amount".into(), field_type: floating_case.into(), }], ..Default::default() }; let result = post_table_definition(&pool, request).await; assert!(result.is_err(), "Should reject floating point: {}", floating_case); assert_eq!(result.unwrap_err().code(), Code::InvalidArgument); } } #[rstest] #[tokio::test] async fn test_decimal_whitespace_variations(#[future] pool: PgPool) { let pool = pool.await; let whitespace_cases = vec![ ("decimal(\t5\t,\t2\t)", true), // tabs ("decimal(\n5\n,\n2\n)", true), // newlines ("decimal( 5\t, 2\n)", true), // mixed whitespace ("decimal(5 2)", false), // missing comma ("decimal(5\t2)", false), // tab instead of comma ]; for (i, (case, should_succeed)) in whitespace_cases.into_iter().enumerate() { let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: format!("whitespace_test_{}", i), columns: vec![ColumnDefinition { name: "amount".into(), field_type: case.into(), }], ..Default::default() }; let result = post_table_definition(&pool, request).await; if should_succeed { assert!(result.is_ok(), "Should handle whitespace: {}", case); } else { assert!(result.is_err(), "Should reject invalid format: {}", case); } } } #[rstest] #[tokio::test] async fn test_decimal_boundary_scale_values(#[future] pool: PgPool) { let pool = pool.await; let boundary_cases = vec![ ("decimal(10, 10)", true), // scale equals precision (valid) ("decimal(10, 11)", false), // scale > precision (invalid, already tested) ("decimal(1, 0)", true), // minimum valid case ("decimal(2, 1)", true), // normal case ]; for (i, (case, should_succeed)) in boundary_cases.into_iter().enumerate() { let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: format!("boundary_test_{}", i), columns: vec![ColumnDefinition { name: "amount".into(), field_type: case.into(), }], ..Default::default() }; let result = post_table_definition(&pool, request).await; if should_succeed { assert!(result.is_ok(), "Should succeed: {}", case); } else { assert!(result.is_err(), "Should fail: {}", case); } } } #[rstest] #[tokio::test] async fn test_decimal_case_insensitive_variations(#[future] pool: PgPool) { let pool = pool.await; let case_variations = vec![ "DECIMAL(5,2)", "Decimal(5,2)", "decimal(5,2)", "DeCiMaL(5,2)", ]; for (i, case_variant) in case_variations.into_iter().enumerate() { let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: format!("case_test_{}", i), columns: vec![ColumnDefinition { name: "amount".into(), field_type: case_variant.into(), }], ..Default::default() }; let result = post_table_definition(&pool, request).await; assert!(result.is_ok(), "Should handle case variation: {}", case_variant); let response = result.unwrap(); assert!(response.sql.contains("NUMERIC(5, 2)"), "Should map to NUMERIC(5, 2): {}", response.sql); } } #[rstest] #[tokio::test] async fn test_decimal_u32_overflow_protection(#[future] pool: PgPool) { let pool = pool.await; // Test numbers that would overflow u32 (> 4,294,967,295) // Your current code uses u32::parse, so these should fail gracefully let overflow_cases = vec![ "decimal(4294967296, 0)", // u32::MAX + 1 "decimal(0, 4294967296)", // u32::MAX + 1 for scale "decimal(99999999999999, 0)", // very large number ]; for overflow_case in overflow_cases { let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: format!("overflow_test_{}", overflow_case.len()), columns: vec![ColumnDefinition { name: "amount".into(), field_type: overflow_case.into(), }], ..Default::default() }; let result = post_table_definition(&pool, request).await; assert!(result.is_err(), "Should reject overflow values: {}", overflow_case); let err = result.unwrap_err(); assert_eq!(err.code(), Code::InvalidArgument); // Should contain either "Invalid precision" or "Invalid scale" assert!(err.message().contains("Invalid") && (err.message().contains("precision") || err.message().contains("scale"))); } }