// 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 profiles p ON td.profile_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_with_preexisting_table: PgPool, ) { // Scenario: Create a table that links to 'customers' and also defines its own 'customers_id' column. // Expected: The generated CREATE TABLE will have a duplicate column, causing a database error. let pool = pool_with_preexisting_table.await; // Provides 'customers' table let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "orders_collision".into(), columns: vec![ColumnDefinition { name: "customers_id".into(), // This will collide with the generated FK field_type: "integer".into(), }], links: vec![TableLink { linked_table_name: "customers".into(), required: true, }], indexes: vec![], }; // Act let result = post_table_definition(&pool, request).await; // Assert let err = result.unwrap_err(); assert_eq!( err.code(), Code::Internal, "Expected Internal error due to duplicate column in CREATE TABLE" ); } #[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) { // Scenario: Test that linking requires using the sanitized name, not the original. let pool = pool.await; let original_name = "My Invoices"; let sanitized_name = "myinvoices"; // 1. Create the table with a name that requires sanitization. let create_req = PostTableDefinitionRequest { profile_name: "default".into(), table_name: original_name.into(), ..Default::default() }; let resp = post_table_definition(&pool, create_req).await.unwrap(); assert!(resp.sql.contains(&format!("gen.\"{}\"", sanitized_name))); // 2. Attempt to link to the *original* name, which should fail. let link_req_fail = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "payments".into(), links: vec![TableLink { linked_table_name: original_name.into(), required: true, }], ..Default::default() }; let err = post_table_definition(&pool, link_req_fail) .await .unwrap_err(); assert_eq!(err.code(), Code::NotFound); assert!(err.message().contains("Linked table My Invoices not found")); // 3. Attempt to link to the *sanitized* name, which should succeed. let link_req_success = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "payments_sanitized".into(), links: vec![TableLink { linked_table_name: sanitized_name.into(), required: true, }], ..Default::default() }; let success_resp = post_table_definition(&pool, link_req_success).await.unwrap(); assert!(success_resp.success); assert!(success_resp .sql .contains(&format!("REFERENCES gen.\"{}\"(id)", sanitized_name))); } // ========= 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 violate the database's NOT NULL constraint on profiles.name. 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::Internal, "Expected Internal error from DB constraint violation" ); assert!( err.message().to_lowercase().contains("profile error"), "Unexpected error message: {}", err.message() ); } // ========= Category 4: Concurrency ========= #[rstest] #[tokio::test] #[ignore = "Concurrency tests can be flaky and require careful setup"] async fn test_race_condition_on_table_creation(#[future] pool: PgPool) { // Scenario: Two requests try to create the exact same table at the same time. // Expected: One succeeds, the other fails with AlreadyExists. let pool = pool.await; let request1 = PostTableDefinitionRequest { profile_name: "concurrent_profile".into(), table_name: "racy_table".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"); }