// tests/table_definition/post_table_definition_test3.rs // NOTE: All 'use' statements have been removed from this file. // They are inherited from the parent file that includes this one. // ========= Helper Functions for this Test File ========= /// Checks that a table definition does NOT exist for a given profile and table name. async fn assert_table_definition_does_not_exist(pool: &PgPool, profile_name: &str, table_name: &str) { let count: i64 = sqlx::query_scalar!( "SELECT COUNT(*) FROM table_definitions td JOIN schemas p ON td.schema_id = p.id WHERE p.name = $1 AND td.table_name = $2", profile_name, table_name ) .fetch_one(pool) .await .expect("Failed to query for table definition") .unwrap_or(0); assert_eq!( count, 0, "Table definition for '{}/{}' was found but should have been rolled back.", profile_name, table_name ); } // ========= Category 2: Advanced Identifier and Naming Collisions ========= #[rstest] #[tokio::test] async fn test_fail_on_column_name_collision_with_fk(#[future] pool: PgPool) { let pool = pool.await; // Use a unique table name to avoid conflicts with other tests let unique_id = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .subsec_nanos(); let customers_table = format!("customers_collision_{}", unique_id); let orders_table = format!("orders_collision_{}", unique_id); // First, create the prerequisite table using the proper API let customers_request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: customers_table.clone(), columns: vec![ColumnDefinition { name: "name".into(), field_type: "text".into(), }], links: vec![], indexes: vec![], }; // Create the customers table let customers_result = post_table_definition(&pool, customers_request).await; assert!(customers_result.is_ok(), "Failed to create prerequisite customers table: {:?}", customers_result); // Now test the collision scenario // This should fail because we're trying to create a "customers_collision_xxxxx_id" column // while also linking to the table (which auto-generates the same foreign key) let fk_column_name = format!("{}_id", customers_table); let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: orders_table, columns: vec![ColumnDefinition { name: fk_column_name.clone(), // This will collide with the generated FK field_type: "integer".into(), }], links: vec![TableLink { linked_table_name: customers_table, required: true, }], indexes: vec![], }; // Act let result = post_table_definition(&pool, request).await; // Assert - this should now fail with InvalidArgument because of the column name validation let err = result.unwrap_err(); assert_eq!( err.code(), Code::InvalidArgument, "Expected InvalidArgument due to column name ending in _id, got: {:?}", err ); // FIXED: More flexible error message check assert!( err.message().contains("Column name") && err.message().contains("cannot be") && err.message().contains("end with '_id'"), "Error message should mention the invalid column name: {}", err.message() ); } #[rstest] #[tokio::test] async fn test_fail_on_duplicate_column_names_in_request(#[future] pool: PgPool) { // Scenario: The request itself contains two columns with the same name. // Expected: Database error on CREATE TABLE with duplicate column definition. let pool = pool.await; let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "duplicate_cols".into(), columns: vec![ ColumnDefinition { name: "product_name".into(), field_type: "text".into(), }, ColumnDefinition { name: "product_name".into(), field_type: "text".into(), }, ], ..Default::default() }; // Act let result = post_table_definition(&pool, request).await; // Assert let err = result.unwrap_err(); assert_eq!(err.code(), Code::Internal); } #[rstest] #[tokio::test] async fn test_link_to_sanitized_table_name(#[future] pool: PgPool) { let pool = pool.await; // FIXED: Use valid table name instead of invalid one let table_name = "my_invoices"; // 1. Create the table with a VALID name let create_req = PostTableDefinitionRequest { profile_name: "default".into(), table_name: table_name.into(), columns: vec![ColumnDefinition { name: "amount".into(), field_type: "text".into(), }], ..Default::default() }; let resp = post_table_definition(&pool, create_req).await.unwrap(); assert!(resp.sql.contains(&format!("\"default\".\"{}\"", table_name))); // 2. Link to the correct name - should succeed let link_req_success = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "payments".into(), columns: vec![ColumnDefinition { name: "amount".into(), field_type: "text".into(), }], links: vec![TableLink { linked_table_name: table_name.into(), required: true, }], ..Default::default() }; let success_resp = post_table_definition(&pool, link_req_success).await.unwrap(); assert!(success_resp.success); } // ========= Category 3: Complex Link and Profile Logic ========= #[rstest] #[tokio::test] async fn test_fail_on_true_self_referential_link(#[future] pool: PgPool) { // Scenario: A table attempts to link to itself in the same request. // Expected: NotFound, because the table definition doesn't exist yet at link-check time. let pool = pool.await; let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "employees".into(), links: vec![TableLink { linked_table_name: "employees".into(), // Self-reference required: false, // For a manager_id FK }], ..Default::default() }; // Act let result = post_table_definition(&pool, request).await; // Assert let err = result.unwrap_err(); assert_eq!(err.code(), Code::NotFound); assert!(err.message().contains("Linked table employees not found")); } #[rstest] #[tokio::test] async fn test_behavior_on_empty_profile_name(#[future] pool: PgPool) { // Scenario: Attempt to create a table with an empty profile name. // Expected: This should be rejected by input validation. let pool = pool.await; let request = PostTableDefinitionRequest { profile_name: "".into(), table_name: "table_in_empty_profile".into(), ..Default::default() }; // Act let result = post_table_definition(&pool, request).await; // Assert let err = result.unwrap_err(); assert_eq!( err.code(), Code::InvalidArgument, // Changed from Internal "Expected InvalidArgument error from input validation" ); assert!( err.message().contains("Profile name cannot be empty"), // Updated message "Unexpected error message: {}", err.message() ); } // ========= Category 4: Concurrency ========= #[rstest] #[tokio::test] async fn test_race_condition_on_table_creation(#[future] pool: PgPool) { let pool = pool.await; // FIXED: Use unique profile and table names to avoid conflicts between test runs let unique_id = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos(); let request1 = PostTableDefinitionRequest { profile_name: format!("concurrent_profile_{}", unique_id), table_name: "racy_table".into(), columns: vec![ColumnDefinition { name: "test_col".into(), field_type: "text".into(), }], ..Default::default() }; let request2 = request1.clone(); let pool1 = pool.clone(); let pool2 = pool.clone(); // Act let (res1, res2) = tokio::join!( post_table_definition(&pool1, request1), post_table_definition(&pool2, request2) ); // Assert let results = vec![res1, res2]; let success_count = results.iter().filter(|r| r.is_ok()).count(); let failure_count = results.iter().filter(|r| r.is_err()).count(); assert_eq!( success_count, 1, "Exactly one request should succeed" ); assert_eq!(failure_count, 1, "Exactly one request should fail"); let err = results .into_iter() .find(|r| r.is_err()) .unwrap() .unwrap_err(); assert_eq!(err.code(), Code::AlreadyExists); assert_eq!(err.message(), "Table already exists in this profile"); }