Files
komp_ac/server/tests/table_definition/post_table_definition_test3.rs

275 lines
8.9 KiB
Rust

// 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");
}